XIV Open Cup named after E.V. Pankratiev. GP of Europe
A. The Motorway
等价于找到最小和最大的$L$满足存在$S$使得$S+(i-1)L\leq a_i\leq S+i\times L$
即
$S\leq\min((1-i)L+a_i)$
$S\geq\max(-i\times L+a_i)$
求出上下凸壳的交点即可,因为斜率本身有序,故时间复杂度为$O(n)$。
#include<cstdio> const int N=1000010,BUF=12000000; const double inf=1e30,eps=1e-9; char Buf[BUF],*buf=Buf; int n,i,j,b[N],ka[N],kb[N],qa[N],ta,qb[N],tb; double ansl=inf,ansr=-inf,pre,A,B,ca[N],cb[N]; inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} inline int sgn(double x){ if(x>eps)return 1; if(x<-eps)return -1; return 0; } inline double posa(int x,int y){return 1.0*(b[y]-b[x])/(ka[x]-ka[y]);} inline double posb(int x,int y){return 1.0*(b[y]-b[x])/(kb[x]-kb[y]);} inline double posab(int x,int y){ if(ka[x]==kb[y])return 1e100; return 1.0*(b[y]-b[x])/(ka[x]-kb[y]); } inline void work(int x,int y,double now){ double o=posab(qa[x],qb[y]); if(sgn(o-pre)>=0&&sgn(o-now)<=0){ if(o<ansl)ansl=o; if(o>ansr)ansr=o; } pre=now; } int main(){ fread(Buf,1,BUF,stdin);read(n); for(i=1;i<=n;i++)read(b[i]),ka[i]=1-i,kb[i]=-i; for(i=1;i<=n;qa[++ta]=i++)while(ta>1&&posa(qa[ta-1],qa[ta])>posa(qa[ta],i))ta--; for(i=n;i;qb[++tb]=i--)while(tb>1&&posb(qb[tb-1],qb[tb])>posb(qb[tb],i))tb--; for(i=1;i<ta;i++)ca[i]=posa(qa[i],qa[i+1]); for(i=1;i<tb;i++)cb[i]=posb(qb[i],qb[i+1]); ca[ta]=cb[tb]=inf; i=j=1,pre=-inf; while(i<=ta&&j<=tb){ A=ca[i],B=cb[j]; if(!sgn(A-B))work(i++,j++,A); else if(A<B)work(i++,j,A); else work(i,j++,B); } return printf("%.12f %.12f",ansl,ansr),0; }
B. Bytehattan
在平面图中,两个点不连通等价于在对偶图中两个点连通,并查集维护。
#include<cstdio> int n,k,i,x,y,a,b,f[2250000],t;char op,s[100]; inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';} int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline int P(int x,int y){return !x||x==n||!y||y==n?0:(x-1)*(n-1)+y;} int main(){ read(n),read(k); for(i=1;i<n*n;i++)f[i]=i; while(k--){ read(x),read(y);while(!(((op=getchar())=='N')||(op=='E'))); if(t){read(x),read(y);while(!(((op=getchar())=='N')||(op=='E')));}else gets(s); a=op=='N'?P(x-1,y):P(x,y-1);b=P(x,y); if(F(a)==F(b))t=1,puts("NIE");else t=0,f[f[a]]=f[b],puts("TAK"); } return 0; }
C. The Carpenter
两个三角形之间必然存在一条横的或者竖的或者斜$45$度的分割线,求出每条分割线两侧的最优解即可。
时间复杂度$O(nm)$。
D. Demonstrations
离散化后用并查集染色维护出每一段被哪几个区间覆盖,然后枚举每段更新答案即可。
#include<cstdio> #include<algorithm> #include<map> using namespace std; const int N=1000010; int n,m,i,a[N],e[N][2],f[N],cnt[N],g[N][2],val[N],ans;map<int,int>T[N]; int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void col(int l,int r,int x){ l=lower_bound(a+1,a+m+1,l)-a; while(1){ l=F(l); if(a[l]>=r)return; cnt[l]++; if(cnt[l]>2)f[l]++; else g[l][cnt[l]-1]=x; l++; } } int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d%d",&e[i][0],&e[i][1]); a[++m]=e[i][0]; a[++m]=e[i][1]; } sort(a+1,a+m+1); for(i=1;i<=m;i++)f[i]=i; for(i=1;i<=n;i++)col(e[i][0],e[i][1],i); for(i=1;i<m;i++){ int x=g[i][0],y=g[i][1],z=a[i+1]-a[i]; if(cnt[i]==1)val[x]+=z; if(cnt[i]==2)T[x][y]+=z; } for(i=1;i<m;i++){ int x=g[i][0],y=g[i][1],z=a[i+1]-a[i]; if(cnt[i]==2)ans=max(ans,val[x]+val[y]+T[x][y]); } sort(val+1,val+n+1); ans=max(ans,val[n-1]+val[n]); printf("%d",ans); }
E. The Exam
按$n$奇偶性构造出相邻差值最大的序列然后判断是否不小于$k$即可,时间复杂度$O(n)$。
#include<cstdio> const int N=1000010,OUT=10000000; int n,m,k,i,j,x,y,w[N],a[N];char Out[OUT],*ou=Out;int Outn[30],Outcnt; inline void write(int x){ if(!x)*ou++=48; else{ for(Outcnt=0;x;x/=10)Outn[++Outcnt]=x%10+48; while(Outcnt)*ou++=Outn[Outcnt--]; } } inline int abs(int x){return x>0?x:-x;} int main(){ scanf("%d%d",&n,&k); for(i=0;i<n;i++)w[i]=i+1; if(n&1){ m=n/2; for(i=j=m;i<n;i++)if(w[i]-w[i-m]<w[j]-w[j-m])j=i; x=j,y=(x+m)%n; for(i=0;i<n;i+=2,x=(x+n-1)%n)a[i]=x; for(i=1;i<n;i+=2,y=(y+n-1)%n)a[i]=y; }else{ for(i=1,j=0;i<n;i+=2,j++)a[i]=j; for(i=0;i<n;i+=2,j++)a[i]=j; } for(i=1;i<n;i++)if(abs(w[a[i]]-w[a[i-1]])<k)return puts("NIE"),0; for(i=0;i<n;i++)write(w[a[i]]),*ou++=' '; fwrite(Out,1,ou-Out,stdout); return 0; }
F. Speed Cameras
不断剥叶子得出每个点的层次,那么前$\lfloor\frac{k}{2}\rfloor$层必选,若$k$为奇数则还要多选一个点。
#include<cstdio> const int N=1000010; int n,m,k,i,x,y,deg[N],d[N],g[N],v[N<<1],nxt[N<<1],ed,h,t,q[N],ans,vis[N]; inline void add(int x,int y){deg[x]++;v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} inline void ext(int x,int y){ deg[x]--; if(deg[x]>1)return; if(!d[x])d[q[++t]=x]=y; } int main(){ scanf("%d%d",&n,&m); for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); for(h=i=1;i<=n;i++)if(deg[i]<=1)ext(i,1); while(h<=t){ x=q[h++]; for(i=g[x];i;i=nxt[i])ext(v[i],d[x]+1); } for(i=1;i<=n;i++)if(d[i]*2<=m)vis[i]=1; for(i=1;i<=n;i++)if(!vis[i]&&(m&1)){vis[i]=1;break;} for(i=1;i<=n;i++)if(vis[i])ans++; printf("%d\n",ans); for(i=1;i<=n;i++)if(vis[i])printf("%d ",i); }
G. Game
若$0$只有$1$个,那么显然无解。
若$0$超过$1$个,那么显然有解。
若$2,3,5,7$这些质数出现次数为奇数,那么显然无解。
那么将剩下每个数出现次数不断减$2$直到$\leq 6$都不会影响结果。
设$f[i][j][k]$表示质数$2$的指数为$i$,$3$的指数为$j$,选了$k$个数是否可能,bitset优化DP即可。
#include<cstdio> typedef long long ll; const int lim=6; int Case,i,j,x,y;ll a[10]; unsigned long long f[lim*7+1][lim*4+1]; bool solve(){ if(a[0]==1)return 0; if(a[0]>1)return 1; if(a[5]&1)return 0; a[5]=0; if(a[7]&1)return 0; a[7]=0; ll c2=a[2]+a[4]*2+a[6]+a[8]*3; if(c2&1)return 0; ll c3=a[3]+a[6]+a[9]*2; if(c3&1)return 0; for(i=0;i<=lim*7;i++)for(j=0;j<=lim*4;j++)f[i][j]=0; f[0][0]=1; int s2=0,s3=0,s=0; for(i=0;i<10;i++){ if(a[i]>lim)a[i]-=(a[i]-lim)/2*2; while(a[i]>lim)a[i]-=2; if(!a[i])continue; int A=0,B=0; if(i==2)A=1; if(i==3)B=1; if(i==4)A=2; if(i==6)A=B=1; if(i==8)A=3; if(i==9)B=2; for(j=0;j<a[i];j++){ s2+=A,s3+=B,s++; for(x=lim*7;x>=A;x--)for(y=lim*4;y>=B;y--)f[x][y]|=f[x-A][y-B]<<1; } } return f[s2/2][s3/2]>>(s/2)&1; } int main(){ scanf("%d",&Case); while(Case--){ for(i=0;i<10;i++)scanf("%lld",&a[i]); puts(solve()?"TAK":"NIE"); } }
H. The Hero
将每个点按存在时间段拆点,共$O(n+p)$个点。
进行Dijkstra求最短路,对于每个拆出来的点枚举所有出边进行松弛。
同时对于每个点维护所有拆点集合set,每次用$l$松弛距离时将其从set中删除。
时间复杂度$O(10(n+q)\log n)$。
#include<cstdio> #include<algorithm> #include<queue> #include<vector> #include<set> using namespace std; typedef long long ll; typedef pair<ll,int>P; const int N=100010,M=1000010; const ll inf=1LL<<60; const int BUF=40000000; 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;} int n,m,cnt,i,j,k,x,y,z,g[N],v[M],w[M],nxt[M],ed,st[N],en[N]; bool vis[N<<1]; struct E{ int x;ll l,r; E(){} E(int _x,ll _l,ll _r){x=_x,l=_l,r=_r;} }e[N],p[N<<1]; set<P>T[N]; ll d[N<<1],ans=inf; priority_queue<P,vector<P>,greater<P> >q; inline bool cmp(const E&a,const E&b){ if(a.x!=b.x)return a.x<b.x; return a.l<b.l; } inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;} inline void ext(int x,ll y){ if(y>p[x].r)return; if(vis[x])return; if(y>=d[x])return; d[x]=y; q.push(P(y,x)); } inline void work(ll R,ll dis,int x,int len){ dis+=len; set<P>::iterator it=T[x].lower_bound(P(dis,0)); if(it==T[x].end())return; if(p[it->second].l<=dis)ext(it->second,dis),it++; while(it!=T[x].end()){ if(p[it->second].l-len>R)return; ext(it->second,p[it->second].l); set<P>::iterator nxt=it; nxt++; T[x].erase(it); it=nxt; } } int main(){ fread(Buf,1,BUF,stdin); read(n),read(m); while(m--)read(x),read(y),read(z),add(x,y,z); read(m); for(i=1;i<=m;i++)read(e[i].x),read(e[i].l),read(e[i].r); sort(e+1,e+m+1,cmp); for(i=1;i<=m;i=j){ for(j=i;j<=m&&e[i].x==e[j].x;j++); ll pre=0; x=e[i].x; st[x]=cnt+1; for(k=i;k<j;k++){ if(e[k].l>pre+1)p[++cnt]=E(x,pre+1,e[k].l-1); pre=max(pre,e[k].r); } p[++cnt]=E(x,pre+1,inf); en[x]=cnt; } for(i=1;i<=n;i++)if(!st[i]){ p[++cnt]=E(i,1,inf); st[i]=en[i]=cnt; } for(i=1;i<=cnt;i++)d[i]=inf; for(i=1;i<=n;i++)for(j=st[i];j<=en[i];j++)T[i].insert(P(p[j].r,j)); ext(st[1],1); while(!q.empty()){ P t=q.top();q.pop(); if(vis[t.second])continue; vis[t.second]=1; for(i=g[p[t.second].x];i;i=nxt[i])work(p[t.second].r,t.first,v[i],w[i]); } for(i=st[n];i<=en[n];i++)ans=min(ans,d[i]); if(ans>=inf)puts("NIE");else printf("%lld",ans-1); }
I. Genetic Engineering
设$f[i]$表示以$i$为开头一段的结尾最多能有多少段,$g[i]$表示以$i$为开头一段的开头最多能有多少段。
则$f[i]=\max(g[>i])+1,g[i]=f[i后面第k个和i数值相同的位置]$。
若有多个最大值,选数值最小的,再有多个选下标最靠前的,这样可以满足字典序最小。
时间复杂度$O(n)$。
#include<cstdio> #include<vector> using namespace std; const int N=1000010; int n,m,i,rk[N],a[N],f[N],g[N],nxt[N],mx,pos; vector<int>v[N]; int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%d",&a[i]); v[a[i]].push_back(i); rk[i]=v[a[i]].size()-1; } mx=0,pos=n+1; for(i=n;i;i--){ f[i]=mx+1; nxt[i]=pos; if(rk[i]+m-1<v[a[i]].size()){ g[i]=f[v[a[i]][rk[i]+m-1]]; if(g[i]>mx||g[i]==mx&&a[i]<=a[pos])mx=g[i],pos=i; } } printf("%d\n",mx*m); while(mx--){ for(i=1;i<=m;i++)printf("%d ",a[pos]); pos=nxt[v[a[pos]][rk[pos]+m-1]]; } }
J. Robin Hood
$ans=\sum_{i=1}^n i-2^{\lfloor\log_2i\rfloor}$,数位DP求出每种$\log$的$i$的个数即可。
#include<cstdio> const int N=50; int n,i,j,k,nj,x,m,a[N];long long ans,f[N][2][N]; int main(){ scanf("%d",&n); m=40; ans=1LL*n*(n+1)/2; for(i=1;i<=m;i++)a[i]=n&1,n>>=1; f[m][0][0]=1; for(i=m;i>1;i--)for(j=0;j<2;j++)for(k=0;k<=m;k++)for(x=0;x<2;x++){ nj=j; if(!j){ if(x>a[i-1])continue; if(x<a[i-1])nj=1; } f[i-1][nj][x?(k?k:(i-1)):k]+=f[i][j][k]; } for(j=0;j<2;j++)for(k=1;k<=m;k++)ans-=f[1][j][k]<<(k-1); printf("%lld",ans); }
K. Blanket
扫描线+线段树统计出每个方格被经过的矩形数目的平方和即可,时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=400010,M=1200000; int n,m,i,X,Y,A,B,ca,ce,a[N],w[M],tag[M];ll wv[M],wvv[M];long double ans; struct E{int x,l,r,t;E(){}E(int _x,int _l,int _r,int _t){x=_x,l=_l,r=_r,t=_t;}}e[N]; inline bool cmp(const E&a,const E&b){return a.x<b.x;} inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';} void build(int x,int a,int b){ w[x]=::a[b]-::a[a]; if(a+1>=b)return; int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid,b); } inline void tag1(int x,int p){ wvv[x]+=2LL*p*wv[x]+1LL*p*p*w[x]; wv[x]+=1LL*p*w[x]; tag[x]+=p; } void change(int x,int a,int b,int c,int d,int p){ if(c<=a&&b<=d){tag1(x,p);return;} if(tag[x])tag1(x<<1,tag[x]),tag1(x<<1|1,tag[x]),tag[x]=0; int mid=(a+b)>>1; if(c<mid)change(x<<1,a,mid,c,d,p); if(d>mid)change(x<<1|1,mid,b,c,d,p); wv[x]=wv[x<<1]+wv[x<<1|1]; wvv[x]=wvv[x<<1]+wvv[x<<1|1]; } int main(){ read(n);read(A),read(B); for(i=1;i<=n;i++){ read(X),read(Y); if(!A||!B)continue; ans-=1LL*A*B; a[++ca]=Y; a[++ca]=Y+B; e[++ce]=E(X,Y,Y+B,1); e[++ce]=E(X+A,Y,Y+B,-1); } sort(e+1,e+ce+1,cmp); sort(a+1,a+ca+1); for(i=1;i<=ca;i++)if(i==1||a[i]!=a[i-1])a[++m]=a[i]; build(1,1,m); for(i=1;i<=ce;i++){ if(i>1&&e[i].x>e[i-1].x)ans+=1.0*wvv[1]*(e[i].x-e[i-1].x); change(1,1,m,lower_bound(a+1,a+m+1,e[i].l)-a,lower_bound(a+1,a+m+1,e[i].r)-a,e[i].t); } return printf("%.15f",(double)(ans/n/(n-1))),0; }