XII Open Cup named after E.V. Pankratiev. GP of Eastern Europe (AMPPZ-2012)
A. Automat
$m$超过$1600$是没用的。
从后往前考虑,设$f[i][j][k]$表示考虑$[i,n]$这些物品,一共花费$j$元钱,买了$k$个物品的最大收益。
时间复杂度$O(n^5)$。
#include<cstdio> const int N=45,M=1605; int n,m,lim,A,B,i,j,k,x,y,z,o,a[N],b[N],f[2][M][N],ans; inline void up(int&a,int b){a<b?(a=b):0;} inline int min(int a,int b){return a<b?a:b;} int main(){ scanf("%d%d",&n,&m); m=min(m,M-5); lim=40; for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=n;i++)scanf("%d",&b[i]); for(o=0;o<2;o++)for(i=0;i<=m;i++)for(j=0;j<=lim;j++)f[o][i][j]=-1; o=0; f[0][0][0]=0; for(i=n;i;i--,o^=1){ A=a[i],B=b[i]; for(j=0;j<=m;j++)for(k=0;k<=lim;k++)f[o^1][j][k]=-1; for(j=0;j<=m;j++)for(x=0;x<=B&&j+x*A<=m;x++)for(k=0;k<=lim;k++)if(~f[o][j][k]) up(f[o^1][j+x*A][min(k+x,lim)],f[o][j][k]+min(k+x,B)*A); } for(i=0;i<=m;i++)for(j=0;j<=lim;j++)up(ans,f[o][i][j]); printf("%d",ans); }
B. Touristic Bureau
将所有景点按照有趣度从小到大排序,设$f[i]$表示走到$i$的最大获利,则$f[i]=\max(f[j]+manhattan(i,j))+c[i](j<i)$。
枚举曼哈顿距离里$x$和$y$的符号,分别维护最大值即可。
时间复杂度$O(nm)$。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; typedef pair<int,int>P; typedef long long ll; const int N=1010,M=N*N; const ll inf=1LL<<60; int n,m,i,j,x,c[N][N];vector<P>v[M]; ll g[4],f[N][N],ans; inline void up(ll&a,ll b){a<b?(a=b):0;} inline void cal(int x,int y){ ll t=0; up(t,x+y+g[0]);//-x-y up(t,x-y+g[1]);//-x+y up(t,-x+y+g[2]);//x-y up(t,-x-y+g[3]);//x+y up(ans,f[x][y]=t+c[x][y]); } inline void ins(int x,int y){ ll t=f[x][y]; up(g[0],t-x-y); up(g[1],t-x+y); up(g[2],t+x-y); up(g[3],t+x+y); } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)for(j=1;j<=m;j++){ scanf("%d",&x); if(x)v[x].push_back(P(i,j)); } for(i=1;i<=n;i++)for(j=1;j<=m;j++)scanf("%d",&c[i][j]); g[0]=g[1]=g[2]=g[3]=-inf; for(i=1;i<M;i++){ for(j=0;j<v[i].size();j++)cal(v[i][j].first,v[i][j].second); for(j=0;j<v[i].size();j++)ins(v[i][j].first,v[i][j].second); } printf("%lld",ans); }
C. Sequence
下标模$k$相同的位置最后要一样,故直接将序列长度压缩成$k$然后DP即可。
#include<cstdio> const int N=1000010; int n,k,i,j,x,cnt[N][2],f[N][2]; inline void up(int&a,int b){a>b?(a=b):0;} int main(){ scanf("%d%d",&n,&k); for(i=1;i<=n;i++){ scanf("%d",&x); cnt[i%k][x&1]++; } for(i=0;i<=k;i++)f[i][0]=f[i][1]=N; f[0][0]=0; for(i=0;i<k;i++)for(j=0;j<2;j++){ up(f[i+1][j],f[i][j]+cnt[i][1]); up(f[i+1][j^1],f[i][j]+cnt[i][0]); } printf("%d",f[k][0]); }
D. DNA
字符集$=1$最优。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=100010; int n,i,b[N];char a[N],ans; int main(){ scanf("%d%s",&n,a+1); for(i=1;i<=n;i++)b[a[i]]++; ans='A'; if(b['C']<b[ans])ans='C'; if(b['T']<b[ans])ans='T'; if(b['G']<b[ans])ans='G'; printf("%d\n",b[ans]); for(i=1;i<=n;i++)putchar(ans); }
E. Evaluation
对表达式递归建树,设$f[i][j]$表示$i$子树内运算结果为$j$的方案数。
对于求幂直接计算。
对于加法可以FFT。
对于乘法可以求出原根后取指标然后转化为加法FFT。
时间复杂度$O(nP\log P)$。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; typedef vector<int>V; const int N=70000,MO=30011; int P,n,i,j,len,po[MO][10],pos[N],A[N],B[N],C[N]; int ind[N],gen[N],G; char a[310]; namespace FFT{ typedef long double ld; const ld pi=acos(-1.0); struct comp{ ld r,i; comp(ld _r=0,ld _i=0){r=_r,i=_i;} comp operator+(const comp&x){return comp(r+x.r,i+x.i);} comp operator-(const comp&x){return comp(r-x.r,i-x.i);} comp operator*(const comp&x){return comp(r*x.r-i*x.i,r*x.i+i*x.r);} comp conj(){return comp(r,-i);} }A[N],B[N]; inline void FFT(comp a[],int n,int t){ for(int i=1;i<n;i++)if(i<pos[i])swap(a[i],a[pos[i]]); for(int d=0;(1<<d)<n;d++){ int m=1<<d,m2=m<<1; ld o=pi*2/m2*t;comp _w(cos(o),sin(o)); for(int i=0;i<n;i+=m2){ comp w(1,0); for(int j=0;j<m;j++){ comp&A=a[i+j+m],&B=a[i+j],t=w*A; A=B-t; B=B+t; w=w*_w; } } } if(t==-1)for(int i=0;i<n;i++)a[i].r/=n; } inline void mul(int*a,int*b,int*c){ int i,j; for(i=0;i<len;i++)A[i]=comp(a[i],b[i]); FFT(A,len,1); for(i=0;i<len;i++){ j=(len-i)&(len-1); B[i]=(A[i]*A[i]-(A[j]*A[j]).conj())*comp(0,-0.25); } FFT(B,len,-1); for(i=0;i<len;i++)c[i]=((long long)(B[i].r+0.5))%MO; } } V dfs(int l,int r){ if(l==r){ V v; v.resize(P); for(int i=0;i<P;i++)v[i]=0; if(a[l]>='0'&&a[l]<='9')v[(a[l]-'0')%P]=1; if(a[l]>='a'&&a[l]<='z')for(int i=0;i<P;i++)v[i]=1; return v; } l++,r--; int al=-1,ar,bl,br,op; if(a[l]!='('){ al=ar=l; bl=l+2; br=r; op=a[l+1]; }else for(int i=l,t=0;i<=r;i++){ if(a[i]=='(')t++; if(a[i]==')'){ t--; if(!t){ al=l; ar=i; bl=i+2; br=r; op=a[i+1]; break; } } } V vl=dfs(al,ar),vr,v; v.resize(P); for(int i=0;i<P;i++)v[i]=0; if(op!='^')vr=dfs(bl,br); if(op=='^'){ int k=a[bl]-'0'; //printf("[%d,%d]^%d\n",al,ar,k); for(int i=0;i<P;i++)(v[po[i][k]]+=vl[i])%=MO; } if(op=='+'){ //printf("[%d,%d]+[%d,%d]\n",al,ar,bl,br); for(int i=0;i<len;i++)A[i]=B[i]=C[i]=0; for(int i=0;i<P;i++)A[i]=vl[i],B[i]=vr[i]; FFT::mul(A,B,C); for(int i=0;i<len;i++)(v[i%P]+=C[i])%=MO; } if(op=='*'){ //printf("[%d,%d]*[%d,%d]\n",al,ar,bl,br); for(int i=0;i<P;i++)v[0]=(1LL*vl[0]*vr[i]+v[0])%MO; for(int i=1;i<P;i++)v[0]=(1LL*vl[i]*vr[0]+v[0])%MO; for(int i=0;i<len;i++)A[i]=B[i]=C[i]=0; for(int i=1;i<P;i++)A[ind[i]]=vl[i],B[ind[i]]=vr[i]; FFT::mul(A,B,C); for(int i=0;i<len;i++)(v[gen[i%(P-1)]]+=C[i])%=MO; } //for(int i=0;i<P;i++)printf("->%d %d\n",i,v[i]); return v; } inline int powmod(int a,int b,int P){ int t=1; for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P; return t; } int getG(int n){ if(n==2)return 1; int i,j,t=0; static int q[N]; for(i=2;1LL*i*i<=n-1;i++)if((n-1)%i==0)q[t++]=i,q[t++]=(n-1)/i; for(i=2;;i++){ for(j=0;j<t;j++)if(powmod(i,q[j],n)==1)break; if(j==t)return i; } } int main(){ scanf("%d%s",&P,a+1); n=strlen(a+1); if(n==1){ if(a[1]=='0')return puts("1"),0; if(a[1]>='a'&&a[1]<='z')return puts("1"),0; return puts("0"),0; } for(len=1;len<P*2;len<<=1); j=__builtin_ctz(len)-1; for(i=0;i<len;i++)pos[i]=pos[i>>1]>>1|((i&1)<<j); for(i=0;i<MO;i++)for(po[i][0]=j=1;j<10;j++)po[i][j]=1LL*po[i][j-1]*i%P; G=getG(P); for(i=0,j=1;i<P-1;i++){ gen[i]=j; ind[j]=i; j=1LL*j*G%P; } V ret=dfs(1,n); printf("%d",ret[0]); }
F. Formula-1
设$s[k]$为第$k$辆车最多能进行的超车次数,$A[k]$表示它前面那些车给$s[k]$的贡献,$B[k]$为后面的,则$A[k]=\sum_{i=1}^{k-1}(a[i]+1)$。
设$b[i]$为第$i$辆车为了到$k+1$这个位置最少需要的超车次数,则$b[k+1]=0,b[i+1]=b[i]+[a[i]\leq b[i]]$,且$B[k]=\sum_{i=k+1}^n \max(a[i]-b[i],0)$。
若对于每个$k$都有$s[k]\geq a[k]$,则可行。
找出最大的$m$满足$A[m]\leq a[m]$,则除了$m$之外所有车都可行,暴力检查$m$即可。
时间复杂度$O(n)$。
#include<cstdio> const int N=1000010,BUF=30000000; int Case,n,i,k,a[N],b[N];long long s[N];char Buf[BUF],*buf=Buf; inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} int main(){ fread(Buf,1,BUF,stdin);read(Case); while(Case--){ read(n); for(i=1;i<=n;i++)read(a[i]); for(s[1]=0,i=2;i<=n;i++)s[i]=s[i-1]+a[i-1]+1; for(i=1;i<=n;i++)if(s[i]<=a[i])k=i; for(b[i=k+1]=0;i<n;i++)b[i+1]=b[i]+(a[i]<=b[i]); for(i=k+1;i<=n;i++)if(a[i]>b[i])s[k]+=a[i]-b[i]; puts(s[k]>=a[k]?"TAK":"NIE"); } return 0; }
G. General
如果凸包大小$\leq 2$,那么特判即可。
否则凸包大小至少为$3$,对于每个询问,首先在凸包上按极角二分出一条边,看看是否在凸包内或者边上。
如果不在,那么往左往右二分出两条切线的位置,然后用叉积前缀和回答询问即可。
时间复杂度$O((n+m)\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=100010; int n,m,ce,i,j,x,y,z,ca,cb,cnt;ll f[N<<1]; struct P{ int x,y; P(){} P(int _x,int _y){x=_x,y=_y;} P operator-(const P&b){return P(x-b.x,y-b.y);} void operator-=(const P&b){*this=*this-b;} bool operator==(const P&b){return x==b.x&&y==b.y;} bool operator!=(const P&b){return x!=b.x||y!=b.y;} }a[N],b,c[N<<1],O; inline bool cmp(const P&a,const P&b){return a.x==b.x?a.y<b.y:a.x<b.x;} inline ll cross(const P&a,const P&b){return 1LL*a.x*b.y-1LL*a.y*b.x;} inline void read(int&a){ char c;bool f=0;a=0; while(!((((c=getchar())>='0')&&(c<='9'))||(c=='-'))); if(c!='-')a=c-'0';else f=1; while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0'; if(f)a=-a; } int convexhull(P*p,int n,P*q){ int i,k,m; for(i=m=0;i<n;q[m++]=p[i++])while(m>1&&cross(q[m-1]-q[m-2],p[i]-q[m-2])<=0)m--; k=m; for(i=n-2;i>=0;q[m++]=p[i--])while(m>k&&cross(q[m-1]-q[m-2],p[i]-q[m-2])<=0)m--; return --m; } inline bool point_on_segment(P p,P a,P b){ return !cross(b-a,p-a)&&1LL*(p.x-a.x)*(p.x-b.x)+1LL*(p.y-a.y)*(p.y-b.y)<=0; } inline int askl(int l,int r,P p){ int t=l++,mid; while(l<=r){ mid=(l+r)>>1; if(cross(c[mid]-p,c[(mid-1+n)%n]-c[mid])<=0)l=(t=mid)+1;else r=mid-1; } return t; } inline int askr(int l,int r,P p){ int t=r--,mid; while(l<=r){ mid=(l+r)>>1; if(cross(c[mid]-p,c[(mid+1)%n]-c[mid])>=0)r=(t=mid)-1;else l=mid+1; } return t; } inline ll solve(P p){ if(n<2)return 0; if(n==2)return abs(f[0]+cross(c[1],p)+cross(p,c[0])); if(point_on_segment(p,c[0],c[n-1]))return f[n-1]; int o=0; if(p.x>0){ int l=1,r=n-1,mid; while(l<=r)if(cross(c[mid=(l+r)>>1],p)>=0)l=(o=mid)+1;else r=mid-1; }else if(p.y>0)o=n-1; if(p.x>=0&&cross(p-c[o],c[o+1]-p)<0)return f[n-1]; if(p.x>=0&&point_on_segment(p,c[o],c[o+1]))return f[n-1]; int l,r; if(p.x>0)l=askl(0,o,p),r=askr(o,n,p);else l=askl(m,n,p),r=askr(0,m,p); if(l>r)r+=n; return f[n-1]+cross(c[l],p)+cross(p,c[r])-f[r-1]+f[l-1]; } int main(){ read(ca),read(cb); for(i=1;i<=ca;i++)read(a[i].x),read(a[i].y); cnt=ca; sort(a+1,a+ca+1,cmp); for(ca=0,i=1;i<=cnt;i++)if(i==1||a[i]!=a[i-1])a[++ca]=a[i]; n=convexhull(a+1,ca,c); for(O=c[0],i=0;i<n;i++)c[i]-=O; for(i=0;i<n;i++)if(c[i].x>=c[m].x)m=i; for(i=0;i<n;i++)c[i+n]=c[i]; for(i=0;i<n+n;i++){ f[i]=cross(c[i],c[i+1]); if(i)f[i]+=f[i-1]; } while(cb--){ read(b.x),read(b.y); ll tmp=solve(b-O); printf("%lld.%lld\n",tmp/2,tmp%2*5); } return 0; }
H. Hydra
设$f[x]$为击杀$x$的最小代价,则$f[x]=\min(k[x],s[x]+\sum(f[x的后继]))$,用SPFA来循环更新即可。
#include<cstdio> typedef long long ll; const int N=200010,M=1000010,P=1048575,BUF=20000000; int n,i,j,x,g[N],v[M],nxt[M],ed,in[N],h,t,q[P+1];ll f[N],f2[N];char Buf[BUF],*buf=Buf; inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} inline void read(ll&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} int main(){ for(fread(Buf,1,BUF,stdin),read(n),i=1;i<=n;i++)for(read(f2[i]),read(f[i]),read(j);j--;add(x,i))read(x); for(i=1;i<=n;i++)for(j=g[i];j;j=nxt[j])f2[v[j]]+=f[i]; for(i=h=1;i<=n;i++)in[q[++t]=i]=1; while(h!=((t+1)&P)){ x=q[h++],h&=P; if(f[x]>f2[x]){ for(i=g[x];i;i=nxt[i]){ f2[j=v[i]]-=f[x]-f2[x]; if(!in[j]){ in[j]=1; q[h=(h-1+P)&P]=j; } } f[x]=f2[x]; } in[x]=0; } return printf("%lld",f[1]),0; }
I. Inversions
线段树优化BFS。
#include<cstdio> #include<algorithm> using namespace std; const int N=1000010,M=2222222; int n,i,j,a[N],vmi[M],vma[M],mx,tot,e[N][2]; int q[N],h,t; bool vis[N]; inline void up(int x){ vmi[x]=min(vmi[x<<1],vmi[x<<1|1]); vma[x]=max(vma[x<<1],vma[x<<1|1]); } void build(int x,int a,int b){ if(a==b){ vmi[x]=vma[x]=::a[a]; return; } int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); up(x); } void clear(int x,int a,int b,int c){ if(a==b){ vis[q[++t]=a]=1; vmi[x]=N,vma[x]=0; return; } int mid=(a+b)>>1; if(c<=mid)clear(x<<1,a,mid,c);else clear(x<<1|1,mid+1,b,c); up(x); } void bigger(int x,int a,int b,int c,int d,int p){ if(vma[x]<p)return; if(a==b){ vis[q[++t]=a]=1; vmi[x]=N,vma[x]=0; return; } int mid=(a+b)>>1; if(c<=mid)bigger(x<<1,a,mid,c,d,p); if(d>mid)bigger(x<<1|1,mid+1,b,c,d,p); up(x); } void smaller(int x,int a,int b,int c,int d,int p){ if(vmi[x]>p)return; if(a==b){ vis[q[++t]=a]=1; vmi[x]=N,vma[x]=0; return; } int mid=(a+b)>>1; if(c<=mid)smaller(x<<1,a,mid,c,d,p); if(d>mid)smaller(x<<1|1,mid+1,b,c,d,p); up(x); } inline void bfs(int S){ mx=S; h=1,t=0; clear(1,1,n,S); while(h<=t){ int x=q[h++]; if(x>mx)mx=x; if(x>1)bigger(1,1,n,1,x-1,a[x]); if(x<n)smaller(1,1,n,x+1,n,a[x]); } } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]); build(1,1,n); for(i=1;i<=n;i++)if(!vis[i]){ bfs(i); tot++; e[tot][0]=i; e[tot][1]=mx; } printf("%d\n",tot); for(i=1;i<=tot;i++){ printf("%d",e[i][1]-e[i][0]+1); for(j=e[i][0];j<=e[i][1];j++)printf(" %d",j); puts(""); } }
J. Procrastination
按时间从后往前贪心安排任务。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=1000010; int n,i;ll cur; struct P{int d,t;}a[N]; inline bool cmp(const P&a,const P&b){return a.t>b.t;} int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d%d",&a[i].d,&a[i].t); sort(a+1,a+n+1,cmp); cur=1LL<<60; for(i=1;i<=n;i++){ if(cur>a[i].t)cur=a[i].t-a[i].d+1; else cur-=a[i].d; } printf("%lld",cur-1); }
K. Rabbits
若$n=3$,那么贪心取最大的$k$个即可。
否则打枪顺序无关,要么第一枪打$1$,要么第一枪打$2$,要么打$3..n$。
第一枪打$1$和$2$时,模拟出变化后的情况,并将次数减$1$,将环当成序列考虑,那么不能在相邻两个位置打枪,若$i$和$i-2$都打枪,那么可以同时打到$i-1$,DP即可。
第一枪打$3..n$时,可以直接将环当成链DP。
时间复杂度$O(nk)$。
#include<cstdio> #include<algorithm> const int N=4010,M=2010; int n,m,K,i,j,a[N*2],b[N],f[2][M][2][2],ans; inline void up(int&a,int b){a<b?(a=b):0;} inline int dp(int flag){ if(!K)return 0; if(flag){ b[2]+=b[1],b[m-1]+=b[m]; b[1]=b[m]=0; } int i,j,x,y,o=0,ret=0; for(j=0;j<=K;j++)for(x=0;x<2;x++)for(y=0;y<2;y++)f[o][j][x][y]=-1; f[o][0][0][0]=0; for(i=0;i<m;i++,o^=1){ for(j=0;j<=K;j++)for(x=0;x<2;x++)for(y=0;y<2;y++)f[o^1][j][x][y]=-1; for(j=0;j<=K;j++)for(x=0;x<2;x++)for(y=0;y<2;y++)if(~f[o][j][x][y]){ up(f[o^1][j][y][0],f[o][j][x][y]); if(!y&&j<K){ if(x)up(f[o^1][j+1][y][1],f[o][j][x][y]+b[i]+b[i+1]); else up(f[o^1][j+1][y][1],f[o][j][x][y]+b[i+1]); } } } for(j=0;j<=K;j++)for(x=0;x<2;x++)for(y=0;y<2;y++)up(ret,f[o][j][x][y]); return ret; } int main(){ scanf("%d%d",&n,&K); if(!K)return puts("0"),0; for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i+n]=a[i]; if(n==3){ std::sort(a+1,a+n+1); for(i=n;i&&K;i--)ans+=a[i],K--; return printf("%d",ans),0; } K=std::min(K,n/2+5); K--; for(i=1;i<=2;i++){ for(m=0,j=i+1;j<i+n;j++)b[++m]=a[j]; up(ans,dp(1)+a[i]); } K++; for(m=0,i=3;i<=n;i++)b[++m]=a[i]; up(ans,dp(0)); return printf("%d",ans),0; }