来自FallDream的博客,未经允许,请勿转载,谢谢,
第一次在cf上打acm...和同校大佬组队打
总共15题,比较鬼畜,最后勉强过了10题。
AB一样的题目,不同数据范围,一起讲吧
你有一个背包,最多装k本书,你在第i天需要拥有一本编号ai的书,但是你可以去商店购买并加入背包。
问你至少要买多少本书?n,k<=400000
显然可以贪心,留下下一次需要的时间最早的书就行了。
#include<iostream> #include<cstdio> #include<queue> #define pa pair<int,int> #define mp(x,y) make_pair(x,y) #define INF 2000000000 #define MN 400000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } priority_queue<pa> q; int n,k,a[MN+5],ne[MN+5],la[MN+5],ans=0; bool in[MN+5]; int main() { n=read();k=read(); for(int i=1;i<=n;++i) a[i]=read(); for(int i=n;i;--i) { ne[i]=la[a[i]]?la[a[i]]:INF; la[a[i]]=i; } for(int i=1;i<=n;++i) { if(in[a[i]]) ++k,q.push(mp(ne[i],a[i])); else { ++ans; while(q.size()==k) in[q.top().second]=0,q.pop(); q.push(mp(ne[i],a[i])); in[a[i]]=1; } } cout<<ans; return 0; }
C. 还是同样的题面,只不过每本书要的钱不一样了,n,k<=80
把一本书留到下一次用看作一个区间覆盖,发现这就是一道k可重区间问题,费用流建图即可
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define S 0 #define MN 80 #define T 81 #define INF 2000000000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int d[MN+5],n,k,head[MN+5],cnt=1,a[MN+5],c[MN+5],ans=0,pi=0,la[MN+5]; struct edge{int to,next,w,c;}e[MN*MN]; bool inq[MN+5],mark[MN+5]; deque<int> q; inline void ins(int f,int t,int w,int c) { e[++cnt]=(edge){t,head[f],w,c}; head[f]=cnt; e[++cnt]=(edge){f,head[t],0,-c};head[t]=cnt; } bool modlabel() { for(int i=S;i<=T;++i) d[i]=INF; d[T]=0;inq[T]=1;q.push_front(T); while(!q.empty()) { int x=q.front();q.pop_front(); for(int i=head[x];i;i=e[i].next) if(e[i^1].w&&e[i^1].c+d[x]<d[e[i].to]) { d[e[i].to]=d[x]+e[i^1].c; if(!inq[e[i].to]) { inq[e[i].to]=1; if(d[e[i].to]<d[q.size()?q.front():0]) q.push_front(e[i].to); else q.push_back(e[i].to); } } inq[x]=0; } for(int i=S;i<=T;++i) for(int j=head[i];j;j=e[j].next) e[j].c+=d[e[j].to]-d[i]; return pi+=d[S],d[S]<INF; } int dfs(int x,int f) { if(x==T) return ans+=pi*f,f; int used=0;mark[x]=1; for(int i=head[x];i;i=e[i].next) if(e[i].w&&!e[i].c&&!mark[e[i].to]) { int w=dfs(e[i].to,min(f-used,e[i].w)); used+=w;e[i].w-=w;e[i^1].w+=w; if(used==f) return used; } return used; } int main() { n=read();k=read()-1; for(int i=1;i<=n;++i) a[i]=read(); for(int i=1;i<=n;++i) c[i]=read(); for(int i=1;i<=n;++i) ans+=c[a[i]]; ins(S,1,k,0);ins(n,T,k,0); for(int i=1;i<n;++i) ins(i,i+1,INF,0); for(int i=1;i<=n;++i) { if(la[a[i]]) { if(la[a[i]]==i-1) ans-=c[a[i]]; else ins(la[a[i]]+1,i,1,-c[a[i]]); } la[a[i]]=i; } while(modlabel()) do memset(mark,0,sizeof(mark)); while(dfs(S,INF)); cout<<ans; return 0; }
DEF比较牛逼 D题wa了个几十次,还剩半分钟的时候居然过了 感觉这种奇奇怪怪的题并没有发言权...
G题是送分的
H题
给你一个数字n(<=1000000),要求你构造两个字符串,使得第二个字符串作为子序列在第一个字符串中的出现次数恰好是n次,且第一个字符串的长度不超过200
考虑用组合数来解决。让第二个串为'aaaaab',那么第一个串中每个B的贡献都是他前面的a的个数选出5个的组合数。只要选择适当的地方插入就行了。
#include<iostream> #include<cstdio> #define MN 50 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int num[MN+5],n,C[MN+5][6]; int main() { n=read();C[0][0]=1; for(int i=1;i<=MN;++i) { C[i][0]=1; for(int j=1;j<=5;++j) C[i][j]=C[i-1][j]+C[i-1][j-1]; } for(int i=MN;i>=5;--i) while(n>=C[i][5]) n-=C[i][5],++num[i]; for(int i=0;i<=MN;putchar('a'),++i) for(int j=1;j<=num[i];++j) putchar('b'); printf(" aaaaab"); return 0; }
I
给定一个长度为n字符串,求每个不同子串的出现次数的平方。
T(T<=10)组数据,n<=100000
建出后缀自动机之后,简单dp一下就行了。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int c[200005][26],step[200005],fail[200005]; long long val[200005];long long sum[200005]; char s[200005]; int cnt=1,last=1,n; void ins(int x) { int p=last,np=++cnt;step[np]=step[last]+1;val[np]=1; for(;p&&!c[p][x];p=fail[p])c[p][x]=np; if(!p)fail[np]=1; else { int q=c[p][x]; if(step[q]==step[p]+1) fail[np]=q; else { int nq=++cnt;step[nq]=step[p]+1; for(int i=0;i<26;i++)c[nq][i]=c[q][i]; fail[nq]=fail[q];fail[q]=fail[np]=nq; for(;c[p][x]==q;p=fail[p])c[p][x]=nq; } } last=np; } int v[200005],rk[200005]; void work() { memset(v,0,sizeof(v)); for(int i=1;i<=cnt;i++)v[step[i]]++; for(int i=1;i<=n;i++)v[i]+=v[i-1]; for(int i=cnt;i;i--) rk[v[step[i]]--]=i; for(int i=cnt;i;i--) val[fail[rk[i]]]+=val[rk[i]]; } bool vis[200000]; long long Dfs(int x) { if(vis[x]) return sum[x]; if(x!=1)sum[x]=1LL*val[x]*val[x];vis[x]=1; for(int i=0;i<26;++i) if(c[x][i]) sum[x]+=Dfs(c[x][i]); return sum[x]; } int main() { for(int T=read();T;--T) { memset(c,0,sizeof(c)); memset(fail,0,sizeof(fail)); memset(step,0,sizeof(step)); memset(vis,0,sizeof(vis)); memset(sum,0,sizeof(sum)); memset(val,0,sizeof(val)); cnt=last=1; scanf("%s",s+1);n=strlen(s+1); for(int i=1;s[i];ins(s[i++]-'a')); work(); cout<<Dfs(1)<<endl; } return 0; }
J是送分的
K
给定一棵树,有边权,你要从1号点出发,要求每个点经过次数不超过k的前提下,经过的边的权值和最大(多次经过只算一次)
n<=100000
显然是一道树形dp,用f[i]表示回到i号点的最大答案,f2[i]表示不回来的最大答案,然后用优先队列保存前k大就行了。
#include<iostream> #include<cstdio> #include<queue> #define mp(x,y) make_pair(x,y) #define pa pair<int,int> #define MN 100000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int n,k,cnt=0,f[MN+5],f2[MN+5],head[MN+5],mark[MN+5]; struct edge{int to,next,w;}e[MN*2+5]; priority_queue<pa,vector<pa>,greater<pa> > q[MN+5],q2[MN+5]; inline void ins(int f,int t,int w) { e[++cnt]=(edge){t,head[f],w};head[f]=cnt; e[++cnt]=(edge){f,head[t],w};head[t]=cnt; } void Dp(int x,int fa) { for(int i=head[x];i;i=e[i].next) if(e[i].to!=fa) { Dp(e[i].to,x); q[x].push(mp(f[e[i].to]+e[i].w,e[i].to)); } int mx=0,mx2=0,mm; while(q[x].size()>k) q[x].pop(); if(q[x].size()==k) mm=q[x].top().first,mark[q[x].top().second]=x; else mm=0; while(q[x].size()>k-1) q[x].pop(); while(!q[x].empty()) { pa now=q[x].top(); f[x]+=now.first; mark[now.second]=x; q[x].pop(); } for(int i=head[x];i;i=e[i].next) if(e[i].to!=fa) { if(mark[e[i].to]==x) mx=max(mx,f2[e[i].to]-f[e[i].to]); else mx2=max(mx2,e[i].w+f2[e[i].to]); } f2[x]=max(f[x],max(f[x]+mx2,f[x]+mx+mm)); } int main() { n=read();k=read(); for(int i=1;i<n;++i) { int x=read()+1,y=read()+1,w=read(); ins(x,y,w); } Dp(1,0); printf("%d ",max(f[1],f2[1])); return 0; }
M
给样例猜题意,输出前k小的数字的和就行了。
N
给定两个长度为n的序列,你要从每个序列中选出k个,并且满足a序列中选出的第i个的位置小等于从b序列中选出的第i个,求最小的和。
n<=2000
考虑二分一个值,并把所有数字减去那个值,通过n^2dp求出最小的情况下最多/最少选出多少个,区间包含了k时输出答案即可
#include<iostream> #include<cstdio> #include<cstring> #define ll long long #define MN 2200 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } ll f[MN+5][MN+5],N[MN+5][MN+5],N2[MN+5][MN+5]; int a[MN+5],b[MN+5],n,k; int main() { n=read();k=read(); for(int i=1;i<=n;++i) a[i]=read(); for(int i=1;i<=n;++i) b[i]=read(); int l=-(1e9),r=1e9,mid; while(l<=r) { mid=1LL*l+r>>1; memset(f,63,sizeof(f)); for(int i=0;i<=n;++i) f[0][i]=f[i][0]=0; for(int i=1;i<=n;++i) for(int j=i;j<=n;++j) { if(f[i-1][j]<f[i][j]) f[i][j]=f[i-1][j],N[i][j]=N[i-1][j],N2[i][j]=N2[i-1][j]; else if(f[i-1][j]==f[i][j]) N[i][j]=max(N[i][j],N[i-1][j]),N2[i][j]=min(N2[i][j],N2[i-1][j]); if(f[i][j-1]<f[i][j]) f[i][j]=f[i][j-1],N[i][j]=N[i][j-1],N2[i][j]=N2[i][j-1]; else if(f[i][j-1]==f[i][j]) N[i][j]=max(N[i][j],N[i][j-1]),N2[i][j]=min(N2[i][j],N2[i][j-1]); if(f[i-1][j-1]+a[i]-mid+b[j]-mid<f[i][j]) f[i][j]=f[i-1][j-1]+a[i]-mid+b[j]-mid,N[i][j]=N[i-1][j-1]+1,N2[i][j]=N2[i-1][j-1]+1; else if(f[i-1][j-1]+a[i]-mid+b[j]-mid==f[i][j]) N[i][j]=max(N[i][j],N[i-1][j-1]+1),N2[i][j]=min(N2[i][j],N2[i-1][j-1]+1); } if(N[n][n]>=k&&N2[n][n]<=k) return 0*printf("%lld",f[n][n]+2LL*k*mid); else if(N[n][n]>k) r=mid-1; else l=mid+1; } return 0; }
O题意相同 数据范围500000
发现这就像一个二分图匹配,并且每个b之和之前的a连边,考虑二分之后用堆来贪心。
从前往后确定每一个b的匹配,往堆里加入a,这个b只能和堆顶配对,如果更优秀的话就配对。但是这可能不是最优的,所以之后还要加入-b来表示推流,更换一个b和这个a配对。
#include<iostream> #include<cstdio> #include<queue> #define ll long long #define mp(x,y) make_pair(x,y) #define pa pair<ll,int> #define MN 500000 using namespace std; inline int read() { int x = 0; char ch = getchar(); while(ch < '0' || ch > '9') ch = getchar(); while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x; } int A[MN+5],B[MN+5],n,m;ll tot; priority_queue<pa,vector<pa>,greater<pa> > q; int Solve(int x) { tot=0;int num=0; for(int i=1;i<=n;++i) { q.push(mp(A[i],0)); ll t=q.top().first,now=B[i]-x; if(now+t<0) { tot+=now+t; q.pop(); q.push(mp(-now,1)); } } while(!q.empty()) num+=q.top().second,q.pop(); return num; } int main() { n=read();m=read(); for(int i=1;i<=n;++i) A[i]=read(); for(int i=1;i<=n;++i) B[i]=read(); int l=0,r=2e9,mid; while(l<=r) { mid=1LL*l+r>>1;int num=Solve(mid); if(num==m) return 0*printf("%lld ",tot+1LL*m*mid); if(num<m) l=mid+1; else r=mid-1; } return 0; }