Problem 1 Graph (graph.cpp/c/pas)
【题目描述】
给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点。
【输入格式】
第 1 行,2 个整数 N,M。 接下来 M 行,每行 2 个整数 Ui,Vi,表示边 ⟨Ui,Vi⟩。点用 1,2,...,N 编号。
【输出格式】
N 个整数 A(1),A(2),...,A(N)。
【样例输入】
4 3
1 2
2 4
4 3
【样例输出】
4 4 3 4
【数据范围】
对于 60% 的数据,1 ≤ N,K ≤ 10^3
对于 100% 的数据,1 ≤ N,M ≤ 10^5。
题解:
暴力+错误tarjan缩点
先写的缩点,对拍半天,补了n个漏洞,跑大数据平均10个WA3个
两个程序就一起交了。略丑。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #define N 100008 using namespace std; int n,m,sumedge; queue<int>q; int head[N]; int ans[N],vis[N]; int top,tim,sumcol; int low[N],dfn[N],Stack[N],instack[N],bel[N],mx[N]; int cd[N],rd[N]; inline int read(){ char ch=getchar();int x=0,f=1; for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0'; return x*f; } struct Edge{ int x,y,nxt; Edge(int x=0,int y=0,int nxt=0): x(x),y(y),nxt(nxt){} }edge[N]; void add(int x,int y){ edge[++sumedge]=Edge(x,y,head[x]); head[x]=sumedge; } void bfs(int x){ memset(vis,0,sizeof(vis)); vis[x]=true; while(!q.empty())q.pop(); q.push(x); while(!q.empty()){ int now=q.front();q.pop(); ans[x]=max(ans[x],now); for(int i=head[now];i;i=edge[i].nxt){ int v=edge[i].y; if(!vis[v]){ q.push(v);vis[v]=1; } } } } void tarjian(int x){ low[x]=dfn[x]=++tim; Stack[++top]=x;instack[x]=true; for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].y; if(instack[v])low[x]=min(low[x],dfn[v]); else if(!dfn[v]){ tarjian(v); low[x]=min(low[x],low[v]); } } if(low[x]==dfn[x]){ sumcol++; while(Stack[top+1]!=x){ bel[Stack[top]]=sumcol; instack[Stack[top]]=false; mx[sumcol]=max(mx[sumcol],Stack[top]); top--; } } } void dfs(int x){ for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].y; if(bel[x]==bel[v])continue; dfs(v); ans[bel[x]]=max(ans[bel[x]],ans[bel[v]]); } return; } int main(){ freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); n=read();m=read(); for(int i=1;i<=m;i++){ int x,y; x=read();y=read(); add(x,y); } if(n<=3000){ for(int i=1;i<=n;i++)bfs(i); for(int i=1;i<=n;i++)printf("%d ",ans[i]); return 0; fclose(stdin);fclose(stdout); } for(int i=1;i<=n;i++)if(!dfn[i])tarjian(i); for(int x=1;x<=n;x++){ for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].y; if(bel[v]!=bel[x]) cd[bel[x]]++,rd[bel[v]]++; } } for(int i=1;i<=n;i++)ans[bel[i]]=mx[bel[i]]; for(int i=1;i<=n;i++)if(!rd[bel[i]])dfs(i); for(int i=1;i<=n;i++)printf("%d ",max(ans[bel[i]],mx[bel[i]])); fclose(stdin);fclose(stdout); return 0; }
缩点后,记录每个点的最大点权,那么某个点的能到达的最大点权就是
所在环的最大点权和能到达的最大点权的Max。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #define N 100008 using namespace std; int n,m,sumedge,sume; queue<int>q; int head[N],hed[N]; int ans[N],vis[N]; int top,tim,sumcol; int low[N],dfn[N],Stack[N],instack[N],bel[N],mx[N]; int cd[N],rd[N]; inline int read(){ char ch=getchar();int x=0,f=1; for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0'; return x*f; } struct Edge{ int x,y,nxt; Edge(int x=0,int y=0,int nxt=0): x(x),y(y),nxt(nxt){} }edge[N]; struct E{ int x,y,nxt; E(int x=0,int y=0,int nxt=0): x(x),y(y),nxt(nxt){} }e[N]; void add(int x,int y){ edge[++sumedge]=Edge(x,y,head[x]); head[x]=sumedge; } void add_(int x,int y){ e[++sume]=E(x,y,hed[x]); hed[x]=sume; } void tarjian(int x){ low[x]=dfn[x]=++tim; Stack[++top]=x;instack[x]=true; for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].y; if(instack[v])low[x]=min(low[x],dfn[v]); else if(!dfn[v]){ tarjian(v); low[x]=min(low[x],low[v]); } } if(low[x]==dfn[x]){ sumcol++; while(Stack[top+1]!=x){ bel[Stack[top]]=sumcol; instack[Stack[top]]=false; mx[sumcol]=max(mx[sumcol],Stack[top]); top--; } } } void dfs(int x){ if(vis[x])return ; vis[x]=true;ans[x]=mx[x]; for(int i=hed[x];i;i=e[i].nxt){ int v=e[i].y; dfs(v); ans[x]=max(ans[x],ans[v]); } } int main(){ // freopen("graph.in","r",stdin); // freopen("graph.out","w",stdout); n=read();m=read(); for(int i=1;i<=m;i++){ int x,y; x=read();y=read(); add(x,y); } for(int i=1;i<=n;i++)if(!dfn[i])tarjian(i); for(int x=1;x<=n;x++){ for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].y; if(bel[v]!=bel[x]) { cd[bel[x]]++,rd[bel[v]]++; add_(bel[x],bel[v]); } } } for(int i=1;i<=n;i++)ans[bel[i]]=mx[bel[i]]; for(int i=1;i<=sumcol;i++)if(!vis[i])dfs(i); for(int i=1;i<=n;i++)printf("%d ",ans[bel[i]]); return 0; }
Problem 2 Incr(incr.cpp/c/pas)
【题目描述】
数列 A1,A2,...,AN,修改最少的数字,使得数列严格单调递增。
【输入格式】
第 1 行,1 个整数 N
第 2 行,N 个整数 A1,A2,...,AN
【输出格式】
1 个整数,表示最少修改的数字
【样例输入】
3
1 3 2
【样例输出】
1
【数据范围】
对于 50% 的数据,N ≤ 10^3
对于 100% 的数据,1 ≤ N ≤ 10^5,1 ≤ Ai ≤ 10^9
题解:
n-最长上升子序列长度
#include<iostream> #include<cstdio> #include<cstring> #define N 100008 using namespace std; int n,ans; int a[N],dp[N]; int main(){ freopen("incr.in","r",stdin); freopen("incr.out","w",stdout); scanf("%d",&ans); for(int i=1;i<=ans;i++)scanf("%d",&a[i]); for(int i=1;i<=ans;i++)dp[i]=1; for(int i=1;i<=ans;i++) for(int j=1;j<i;j++) if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1); for(int i=1;i<=ans;i++)n=max(n,dp[i]); ans-=n; cout<<ans<<endl; fclose(stdin);fclose(stdout); return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=0x3f3f3f3f; int n,a[1000009],dp[1000009]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;i++) { int p=upper_bound(dp+1,dp+n+1,a[i])-dp; if(a[i]!=dp[p-1])//严格上升序列 dp[p]=a[i]; } for(int i=1;i<=n+1;i++) { if(dp[i]==maxn) { printf("%d ",n-(i-1)); return 0; } } return 0; }
Problem 3 Permutation (permutation.cpp/c/pas)
【题目描述】
将 1 到 N 任意排列,然后在排列的每两个数之间根据他们的大小关系插入“>”和“<”。
问在所有排列中,有多少个排列恰好有K个“<”。
例如排列(3, 4, 1, 5, 2)
3 < 4 > 1 < 5 > 2
共有2个“<”
【输入格式】
N,K
【输出格式】
答案
【样例输入】
5 2
【样例输出】
66
【数据范围】
20%:N <= 10
50%:答案在0..2^63-1内
100%:K < N <= 100
题解:
20暴力
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; int n,k; int a[100]; LL ans; int main(){ freopen("permutation.in","r",stdin); freopen("permutation.out","w",stdout); scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)a[i]=i; do{ int js=0; for(int i=2;i<=n;i++) if(a[i]>a[i-1])js++; if(js==k)ans++; }while(next_permutation(a+1,a+n+1)); cout<<ans<<endl; fclose(stdin);fclose(stdout); return 0; }
f[i][j]表示前i个位置放j个小于号。
i个数一共有i+1个位置,那么插入当前第i个数
可能会产生小于号,也可能不会产生小于号
不会产生小于号的情况是插在小于号上,一共有j+1个位置,
会产生的有i-j个位置。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,k; int f[1020][1020]; int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)f[i][0]=1; for(int i=1;i<=n;i++) for(int j=1;j<=k;j++) f[i][j]+=(f[i-1][j]*(j+1)+f[i-1][j-1]*(i-j))%2015; cout<<f[n][k]; return 0; }