A. Candy Game
显然最优策略是一个一个吃,故比较哪种糖果的个数比较多即可。
#include<cstdio> int T,n,i,x,sum; int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); sum=0; for(i=1;i<=n;i++)scanf("%d",&x),sum+=x; for(i=1;i<=n;i++)scanf("%d",&x),sum-=x; puts(sum>0?"BaoBao":"DreamGrid"); } }
B. PreSuffix
对所有串建立AC自动机,那么若前缀$i$是前缀$j$的后缀,说明$i$是Fail树上$j$的祖先。
所以对于询问$(x,y)$,答案就是两点在Fail树上的LCA在原Trie中子树内的字符串总数。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=100010,M=500010; char s[M]; int tot,son[M][26],fail[M],q[M],val[M],G[M],NXT[M],size[M],heavy[M],top[M],d[M]; int n,i,j,m,x,y,pos[N]; inline void ins(int p){ int l=strlen(s),x=0; for(int i=0,w;i<l;i++){ if(!son[x][w=s[i]-‘a‘])son[x][w]=++tot; x=son[x][w]; val[x]++; } pos[p]=x; } void make(){ int h=1,t=0,i,j,x;fail[0]=-1; for(i=0;i<26;i++)if(son[0][i])q[++t]=son[0][i]; while(h<=t)for(x=q[h++],i=0;i<26;i++) if(son[x][i])fail[son[x][i]]=son[fail[x]][i],q[++t]=son[x][i]; else son[x][i]=son[fail[x]][i]; } void dfs(int x){ size[x]=1; for(int i=G[x];i;i=NXT[i]){ d[i]=d[x]+1; dfs(i); size[x]+=size[i]; if(heavy[x]<0)heavy[x]=i; else if(size[i]>size[heavy[x]])heavy[x]=i; } } void dfs2(int x,int y){ top[x]=y; if(~heavy[x])dfs2(heavy[x],y); for(int i=G[x];i;i=NXT[i])if(i!=heavy[x])dfs2(i,i); } inline int lca(int x,int y){ for(;top[x]!=top[y];x=fail[top[x]])if(d[top[x]]<d[top[y]])swap(x,y); return d[x]<d[y]?x:y; } inline void ask(int x,int y){ x=pos[x]; y=pos[y]; int z=lca(x,y); if(!val[z])puts("N");else printf("%d\n",val[z]); } int main(){ while(~scanf("%d",&n)){ for(i=1;i<=n;i++)scanf("%s",s),ins(i); make(); for(i=1;i<=tot;i++)NXT[i]=G[fail[i]],G[fail[i]]=i; for(i=0;i<=tot;i++)heavy[i]=-1; dfs(0); dfs2(0,0); scanf("%d",&m); while(m--)scanf("%d%d",&x,&y),ask(x,y); for(i=0;i<=tot;i++){ for(fail[i]=G[i]=j=0;j<26;j++)son[i][j]=0; val[i]=size[i]=top[i]=d[i]=0; heavy[i]=-1; } tot=0; } return 0; }
C. An Unsure Catch
考虑$k=0$的情形:
对于每个连通块,断开一条环边变成树,求出树上每个点的深度$d$。
设环长为$len$,则该连通块中最优解为$d\bmod len$中出现次数最多的那一个。
考虑$k>0$的情形,有两种最优策略:
$1.$ 利用一步操作将某个环长修改为$1$,再用$k-1$步操作把其它$k-1$个连通块合并上来。
故此时答案为连通块点数最大的$k$个的点数之和。
$2.$ 不刻意制造长度为$1$的环,直接用$k$步操作把$k-1$个连通块合并到某个点上。
枚举每个连通块作为最终合并点,假设其环长为$K$,则要选$k-1$个其它连通块,将它们各断开一条边,然后将环长修改为$k$,使得$d\bmod K$中出现次数最大值最大。
注意到$K$的取值只有$O(\sqrt{n})$种,枚举每个$K$,对于每个连通块计算最优解,然后计数排序统计前$k$大值的和即可。
对于每个连通块,假设环长为$len$,那么按顺序依次断开每条环边后,对断开点子树内所有$d$的影响是整体减去$len$,共$O(n)$次修改和$O(n)$次查询。
注意到每次修改时,只会将某个数加$1$或减$1$,故最大值也只会变化$1$,记录每种值有多少个即可判断最大值是否需要变化,时间复杂度$O(1)$。
总时间复杂度$O(n\sqrt{n})$。
#include<cstdio> #include<algorithm> using namespace std; typedef pair<int,int>P; const int N=100010,inf=100000000; int Case,n,i,a[N],g[N],nxt[N]; bool vis[N],visit[N],on[N]; int d[N]; int f[N]; int id[N],m,ce; int fin[N]; int vs[N]; bool need[N]; int len[N],head[N],cur; P pool[N]; int nowmx,cf[N],have[N]; inline void add(int x,int y){nxt[y]=g[x];g[x]=y;} void dfs(int x){ if(vis[x])return; vis[x]=1; id[++m]=x; for(int i=g[x];i;i=nxt[i])dfs(i); dfs(a[x]); } int cal(int x){ if(d[x])return d[x]; return d[x]=cal(a[x])+1; } inline void solve(){ int x=id[1]; while(!visit[x])visit[x]=1,x=a[x]; int circle=0; while(!on[x]){ circle++; d[x]=inf-circle; on[x]=1,x=a[x]; } ce++; vs[ce]=m; need[circle]=1; len[ce]=circle; head[ce]=x; for(int i=1;i<=m;i++)pool[++cur]=P(ce,cal(id[i])); } inline void up(int&a,int b){a<b?(a=b):0;} inline void addf(int x){ cf[++f[x]]++; up(nowmx,f[x]); } inline void delf(int x){ cf[f[x]--]--; if(!cf[nowmx])nowmx--; } int MO,L; void update(int x){ delf(d[x]%MO); addf((d[x]-L)%MO); for(int i=g[x];i;i=nxt[i])if(!on[i])update(i); } inline void mustlen(int K){ MO=K; int i,j,k=0,x; int hismax=0; for(i=1;i<=n+1;i++)have[i]=0; for(i=1;i<=n;i=j){ for(j=i;j<=n&&pool[i].first==pool[j].first;j++); int l=i,r=j-1; k++; nowmx=0; L=len[k]; int best=0; for(x=l;x<=r;x++)addf(pool[x].second%K); best=nowmx; int S=x=head[k]; while(1){ update(x); up(best,nowmx); x=a[x]; if(x==S)break; } for(x=l;x<=r;x++)delf((pool[x].second-L)%K); have[best]++; if(L==K&&best>hismax)hismax=best; } have[hismax]--; up(fin[k=0],hismax); for(i=n;i;i--)for(j=have[i];j;j--)up(fin[++k],hismax+=i); for(k++;k<=n;k++)up(fin[k],hismax); } int main(){ scanf("%d",&Case); cf[0]=1e9; while(Case--){ scanf("%d",&n); for(i=0;i<=n;i++)g[i]=vis[i]=visit[i]=on[i]=d[i]=f[i]=id[i]=need[i]=0; m=ce=cur=0; for(i=1;i<=n;i++)scanf("%d",&a[i]),add(a[i],i); for(i=1;i<=n;i++)if(!vis[i])m=0,dfs(i),solve(); sort(vs+1,vs+ce+1);reverse(vs+1,vs+ce+1); for(i=1;i<=ce;i++)vs[i]+=vs[i-1]; sort(pool+1,pool+cur+1); for(i=0;i<=n;i++)fin[i]=vs[min(ce,i)]; for(i=1;i<=n;i++)if(need[i])mustlen(i); int k=0; for(i=0;i<=n;i++){ fin[i]=min(fin[i],n); k=i; if(fin[i]>=n)break; } printf("%d\n",k); for(i=0;i<=k;i++)printf("%d%c",fin[i],i<k?‘ ‘:‘\n‘); } }
D. Seat Assignment
$lcm(1,2,...,10)=2520$,对于模$lcm$的每个余数$k$分析其是否是$1$到$10$的倍数,可以发现一共$48$种本质不同的情况。
那么计算出每种情况的容量之后,就转化成了左边$10$个点,右边$48$个点的最大流。
#include<cstdio> const int N=10000,inf=~0U>>2; int lcm,m,i,j,Case,lim[N],q[N],mask[N],vis[N],cnt; int S,T,h[N],gap[N],maxflow; struct E{int t,f;E*nxt,*pair;}*g[N],*d[N],pool[100000],*cur=pool; inline int min(int a,int b){return a<b?a:b;} inline void add(int s,int t,int f){ E*p=cur++;p->t=t;p->f=f;p->nxt=g[s];g[s]=p; p=cur++;p->t=s;p->f=0;p->nxt=g[t];g[t]=p; g[s]->pair=g[t];g[t]->pair=g[s]; } int sap(int v,int flow){ if(v==T)return flow; int rec=0; for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){ int ret=sap(p->t,min(flow-rec,p->f)); p->f-=ret;p->pair->f+=ret;d[v]=p; if((rec+=ret)==flow)return flow; } if(!(--gap[h[v]]))h[S]=T; gap[++h[v]]++;d[v]=g[v]; return rec; } int main(){ lcm=2520; for(i=0;i<lcm;i++){ //k%lcm=i int S=0; for(j=1;j<=10;j++)if(i%j==0)S|=1<<j; if(!vis[S]){ vis[S]=++cnt; q[cnt]=S; } mask[i]=vis[S]; } scanf("%d",&Case); while(Case--){ scanf("%d",&m); for(i=1;i<=cnt;i++)lim[i]=0; int sum=0; for(i=0;i<lcm;i++){ int now=m-i; if(now<0)now=0; now=now/lcm; if(i&&i<=m)now++; lim[mask[i]]+=now; } S=cnt+11; T=S+1; for(cur=pool,i=1;i<=T;i++)g[i]=d[i]=NULL,h[i]=gap[i]=0; for(i=1;i<=cnt;i++)add(S,i,lim[i]); for(i=1;i<=10;i++){ int x; scanf("%d",&x); add(cnt+i,T,x); if(x)for(j=1;j<=cnt;j++)if(q[j]>>i&1)add(j,cnt+i,x); } for(gap[maxflow=0]=T,i=1;i<=T;i++)d[i]=g[i]; while(h[S]<T)maxflow+=sap(S,inf); printf("%d\n",maxflow); } } /* 2^10 %1=0 %2= 1..m %lcm lcm=8*9*5*7 */
E. Yet Another Data Structure Problem
线段树维护序列乘积$mul$,标记$(a,b)$表示$mul=a\times mul^b$。
时间复杂度$O(n\log n\log P)$。
#include<cstdio> const int N=100010,M=262150,P=1000000007; int Case,n,m,i,a[N],ans; int v[M],ta[M],tb[M],len[M]; inline int po(int a,int b){ int t=1; for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P; return t; } inline void tag1(int x,int a,int b){ v[x]=1LL*po(v[x],b)*po(a,len[x])%P; ta[x]=1LL*po(ta[x],b)*a%P; tb[x]=1LL*tb[x]*b%(P-1); } inline void pb(int x){ if(ta[x]==1&&tb[x]==1)return; tag1(x<<1,ta[x],tb[x]); tag1(x<<1|1,ta[x],tb[x]); ta[x]=tb[x]=1; } inline void up(int x){v[x]=1LL*v[x<<1]*v[x<<1|1]%P;} void build(int x,int a,int b){ ta[x]=tb[x]=1; len[x]=b-a+1; if(a==b){v[x]=::a[a];return;} int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); up(x); } void change(int x,int a,int b,int c,int d,int A,int B){ if(c<=a&&b<=d){tag1(x,A,B);return;} pb(x); int mid=(a+b)>>1; if(c<=mid)change(x<<1,a,mid,c,d,A,B); if(d>mid)change(x<<1|1,mid+1,b,c,d,A,B); up(x); } void ask(int x,int a,int b,int c,int d){ if(c<=a&&b<=d){ans=1LL*ans*v[x]%P;return;} pb(x); int mid=(a+b)>>1; if(c<=mid)ask(x<<1,a,mid,c,d); if(d>mid)ask(x<<1|1,mid+1,b,c,d); } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&a[i]); build(1,1,n); while(m--){ int op,l,r,k; scanf("%d%d%d",&op,&l,&r); if(op==1){ scanf("%d",&k); change(1,1,n,l,r,k,1); } if(op==2){ scanf("%d",&k); change(1,1,n,l,r,1,k); } if(op==3){ ans=1; ask(1,1,n,l,r); printf("%d\n",ans); } } } }
F. The Limit
如果分子分母不同时为$0$,那么极限显然,否则根据洛必达法则分别求导计算。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const int N=15; char s[100000]; ll X; struct Poly{ ll v[N]; Poly(){memset(v,0,sizeof v);} void read(){ memset(v,0,sizeof v); scanf("%s",s); int len=strlen(s); int i,j; for(i=0;i<len;){ if(s[i]==‘x‘){ if(s[i+1]==‘^‘){ v[s[i+2]-‘0‘]++; i+=3; continue; } v[1]++; i++; continue; } if(s[i]==‘+‘||s[i]==‘-‘){ int flag=s[i]==‘+‘?1:-1; if(s[i+1]==‘x‘){ if(s[i+2]==‘^‘){ v[s[i+3]-‘0‘]+=flag; i+=4; continue; } v[1]+=flag; i+=2; continue; }else{ //is number ll num=flag*(s[i+1]-‘0‘); if(s[i+2]==‘x‘){ if(s[i+3]==‘^‘){ v[s[i+4]-‘0‘]+=num; i+=5; continue; } v[1]+=num; i+=3; continue; }else{ v[0]+=num; i+=2; continue; } } } //is number ll num=s[i]-‘0‘; if(s[i+1]==‘x‘){ if(s[i+2]==‘^‘){ v[s[i+3]-‘0‘]+=num; i+=4; continue; } v[1]+=num; i+=2; continue; }else{ v[0]+=num; i++; continue; } } } ll f(ll x){ ll ret=0,t=1; for(int i=0;i<N;i++){ ret+=v[i]*t; t*=x; } return ret; } void write(){ for(int i=0;i<N;i++)if(v[i])printf("%lldx^%d ",v[i],i);puts("."); } void dao(){ for(int i=1;i<N;i++)v[i-1]=v[i]*i; v[N-1]=0; } }A,B; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} void solve(){ A.read(); B.read(); scanf("%lld",&X); while(1){ ll U=A.f(X),D=B.f(X); if(!U&&D){ puts("0"); return; } if(U&&!D){ puts("INF"); return; } if(U&&D){ ll d=gcd(abs(U),abs(D)); U/=d,D/=d; if(D<0)U*=-1,D*=-1; if(D!=1)printf("%lld/%lld\n",U,D);else printf("%lld\n",U); return; } A.dao(); B.dao(); } } int main(){ int Case; scanf("%d",&Case); while(Case--)solve(); }
G. It‘s High Noon
假设人位于$(x,y)$,若攻击部分背对原点,那么显然最优情况下$(x,y)=(0,0)$。将所有点极角排序然后双指针枚举即可。注意特判点在原点的情况。
若攻击部分面向原点,那么显然最优情况下$x^2+y^2=R^2$,且直线与圆相切。抠除圆内部的所有点之后,对于剩下的点求出切线角度,那么角度在某个区间内的直线都能取到这个点,扫描线统计即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=500000; const double pi=acos(-1.0),PI=acos(-1.0)*2.0,eps=1e-9; int Case,n,K,i,ans; struct P{ int x,y; }a[N],b[N]; inline int pos(int x,int y){return x?x>0:y>0;} inline int cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;} inline bool cmp(const P&a,const P&b){ if(pos(a.x,a.y)!=pos(b.x,b.y))return pos(a.x,a.y)<pos(b.x,b.y); return cross(a,b)<0; } struct E{ double x;int y; E(){} E(double _x,int _y){x=_x,y=_y;} }e[N]; inline bool cmpe(const E&a,const E&b){return a.x<b.x;} void solve_center(){ int atcenter=0; int m=0; int i,j; for(i=1;i<=n;i++)if(!a[i].x&&!a[i].y)atcenter++;else{ b[++m]=a[i]; } ans=max(ans,atcenter); sort(b+1,b+m+1,cmp); for(i=1;i<=m;i++)b[i+m]=b[i]; for(i=j=1;i<=m;i++){ if(j<i)j=i; while(j+1<i+m&&cross(b[i],b[j+1])<=0)j++; ans=max(ans,j-i+1+atcenter); } } inline int sgn(double x){ if(x>eps)return 1; if(x<-eps)return -1; return 0; } inline void fix(double&x){ while(sgn(x)<0)x+=PI; while(sgn(x-PI)>=0)x-=PI; x=max(x,(double)0.0); } const double eps2=eps*5; void solve_ka(){ int all=0; int i,j,k; int m=0; for(i=1;i<=n;i++){ int dis=a[i].x*a[i].x+a[i].y*a[i].y; if(dis>K){ double o=atan2(-a[i].y,-a[i].x); double w=acos(sqrt(max(1.0-1.0*K/dis,0.0))); w=fabs(w); double A=o-w,B=o+w+pi; double l=A,r=B; l-=eps2,r+=eps2; fix(l); fix(r); if(!sgn(l-r))r=l+eps; //printf("%.10f %.10f\n",l,r); if(l>r)all++; //[l..r] 1 else 0 e[++m]=E(l,1); e[++m]=E(r,-1); }else all++; } ans=max(ans,all); sort(e+1,e+m+1,cmpe); for(i=1;i<=m;i++){ all+=e[i].y; ans=max(ans,all); } } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&K);K*=K; for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y); ans=0; solve_center(); solve_ka(); printf("%d\n",ans); } }
H. Traveling Plan
对于每个点$x$求出$d_x$表示离它最近的补给站到它的距离。
对于每条边$(x,y,w)$,将边权重置为$d_x+d_y+w$。
那么对于询问$(x,y)$,答案就是最小生成树上两点间边权的最大值。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> #include<vector> #include<queue> using namespace std; typedef long long ll; typedef pair<ll,int>P; const int N=100010,M=200010,K=20; const ll inf=1LL<<60; int n,m,q,x,y,i,j,f[N],g[M<<1],v[M<<1],nxt[M<<1],ed;ll w[M<<1]; int d[N],fa[N][K]; ll fw[N][K]; ll dis[N]; bool vis[N]; priority_queue<P,vector<P>,greater<P> >Q; struct E{int x,y;ll z;}e[M]; inline bool cmp(const E&a,const E&b){return a.z<b.z;} int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void add(int x,int y,ll z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ vis[x]=1; for(int i=1;i<K;i++){ fa[x][i]=fa[fa[x][i-1]][i-1]; fw[x][i]=max(fw[x][i-1],fw[fa[x][i-1]][i-1]); } for(int i=g[x];i;i=nxt[i])if(v[i]!=y){ fa[v[i]][0]=x; fw[v[i]][0]=w[i]; d[v[i]]=d[x]+1; dfs(v[i],x); } } inline ll ask(int x,int y){ ll ret=0; if(d[x]<d[y])swap(x,y); for(int i=K-1;~i;i--)if(d[fa[x][i]]>=d[y]){ ret=max(ret,fw[x][i]); x=fa[x][i]; } if(x==y)return ret; for(int i=K-1;~i;i--)if(fa[x][i]!=fa[y][i]){ ret=max(ret,max(fw[x][i],fw[y][i])); x=fa[x][i]; y=fa[y][i]; } return max(ret,max(fw[x][0],fw[y][0])); } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%d",&x); if(x==1){ Q.push(P(0,i)); }else{ dis[i]=inf; } } for(i=1;i<=m;i++){ scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].z); add(e[i].x,e[i].y,e[i].z); add(e[i].y,e[i].x,e[i].z); } while(!Q.empty()){ P t=Q.top();Q.pop(); if(dis[t.second]<t.first)continue; for(i=g[t.second];i;i=nxt[i])if(dis[v[i]]>t.first+w[i])Q.push(P(dis[v[i]]=t.first+w[i],v[i])); } for(i=1;i<=m;i++)e[i].z+=dis[e[i].x]+dis[e[i].y]; sort(e+1,e+m+1,cmp); for(i=1;i<=n;i++)g[i]=0; ed=0; for(i=1;i<=n;i++)f[i]=i; for(i=1;i<=m;i++)if(F(e[i].x)!=F(e[i].y)){ f[f[e[i].x]]=f[e[i].y]; add(e[i].x,e[i].y,e[i].z); add(e[i].y,e[i].x,e[i].z); } for(i=1;i<=n;i++)if(!vis[i])dfs(i,0); scanf("%d",&q); while(q--)scanf("%d%d",&x,&y),printf("%lld\n",ask(x,y)); }
I. Wooden Bridge
留坑。
J. Distance
枚举$O(n^2)$对区间右端点,左端点的取值满足单调性,双指针即可。
时间复杂度$O(n^2)$。
#include<cstdio> typedef long long ll; const int N=1010; int Case,n,p,i,j,k,ans,g[N*3];ll a[N],b[N],f[N][N],w[N*3],V; inline ll cal(ll x){ if(x<0)x=-x; ll t=1; for(int i=0;i<p;i++)t*=x; return t; } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%lld%d",&n,&V,&p); for(i=1;i<=n;i++)scanf("%lld",&a[i]); for(i=1;i<=n;i++)scanf("%lld",&b[i]); ans=0; for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=cal(a[i]-b[j]); for(i=0;i<=n+n+5;i++)g[i]=w[i]=0; for(i=1;i<=n;i++)for(j=1;j<=n;j++){ k=i-j+n; g[k]++; w[k]+=f[i][j]; int G=g[k];ll W=w[k]; while(G>0&&W>V){ G--; W-=f[i-G][j-G]; } g[k]=G; w[k]=W; ans+=G; } printf("%d\n",ans); } }