X Open Cup named after E.V. Pankratiev. European Grand Prix
A. Arithmetic Rectangle
对于一行或者一列的情况可以递推求出最大值。
对于至少一行或者一列的情况,可以定义四个格子一组横向和纵向的相等关系,然后悬线法求最大子矩阵。
时间复杂度$O(nm)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=3005; int Case,n,m,i,j,k,ans,a[N][N],dp[N],f[N],g[N],q[N],t; inline bool same0(int A,int B,int C,int D){//(i,j) (i+1,j+1) if(a[A][B]-a[A+1][B]!=a[C][D]-a[C+1][D])return 0; if(a[A][B+1]-a[A+1][B+1]!=a[C][D+1]-a[C+1][D+1])return 0; return 1; } inline bool same1(int A,int B,int C,int D){//(i,j) (i+1,j+1) if(a[A][B]-a[A][B+1]!=a[C][D]-a[C][D+1])return 0; if(a[A+1][B]-a[A+1][B+1]!=a[C+1][D]-a[C+1][D+1])return 0; return 1; } inline void solve(int l,int r){ int i; q[t=0]=l-1; for(i=l;i<=r;q[++t]=i++){ while(t&&f[q[t]]>=f[i])t--; g[i]=q[t]; } q[t=0]=r+1; for(i=r;i>=l;q[++t]=i--){ while(t&&f[q[t]]>=f[i])t--; ans=max(ans,(f[i]+1)*(q[t]-g[i])); } } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); ans=min(n,2)*min(m,2); for(i=1;i<=n;i++)for(j=1;j<=m;j++)scanf("%d",&a[i][j]); for(i=1;i<=n;i++)for(j=1;j<=m;j++){ dp[j]=1; if(j>1)dp[j]=2; if(j>2&&a[i][j-2]-a[i][j-1]==a[i][j-1]-a[i][j])dp[j]=dp[j-1]+1; ans=max(ans,dp[j]); } for(j=1;j<=m;j++)for(i=1;i<=n;i++){ dp[i]=1; if(i>1)dp[i]=2; if(i>2&&a[i-2][j]-a[i-1][j]==a[i-1][j]-a[i][j])dp[i]=dp[i-1]+1; ans=max(ans,dp[i]); } n--,m--; if(n&&m){ for(j=1;j<=m;j++)f[j]=0; for(i=1;i<=n;i++){ for(j=1;j<=m;j++)if(same0(i,j,i-1,j))f[j]++;else f[j]=1; for(j=1;j<=m;j=k){ for(k=j;k<=m&&same1(i,j,i,k);k++); solve(j,k-1); } } } printf("%d\n",ans); } }
B. Bytean Road Race
对于每个询问,首先特判显然的情况。
假设要从$x$走到$y$,那么从$x$开始不断朝右下走,碰到分岔则往右走,可以倍增求出离$y$最近的点$R$,同理可以求出碰到分岔往下走时离$y$最近的点$D$。
若$R$或者$D$在$y$右下方,那么显然$x$不能到达$y$,否则它们经过的路线若能围住$y$,则可行。
这意味着$R$再走一步以及$D$再走一步能围住$y$,直接判断即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100010,M=17; int n,m,q,i,j,x,y,loc[N][2],r[M][N],d[M][N]; inline bool check(int x,int y){ if(loc[x][0]>loc[y][0])return 0; if(loc[x][1]<loc[y][1])return 0; int R=x,D=x; for(int i=M-1;~i;i--){ if(loc[r[i][R]][0]<=loc[y][0]&&loc[r[i][R]][1]>=loc[y][1])R=r[i][R]; if(loc[d[i][D]][0]<=loc[y][0]&&loc[d[i][D]][1]>=loc[y][1])D=d[i][D]; } if(loc[R][0]>loc[y][0])return 0; if(loc[R][1]<loc[y][1])return 0; if(loc[D][0]>loc[y][0])return 0; if(loc[D][1]<loc[y][1])return 0; if(loc[R][0]<loc[y][0]&&loc[r[0][R]][0]==loc[R][0])return 0; if(loc[D][1]>loc[y][1]&&loc[d[0][D]][1]==loc[D][1])return 0; return 1; } int main(){ scanf("%d%d%d",&n,&m,&q); for(i=1;i<=n;i++)scanf("%d%d",&loc[i][0],&loc[i][1]); while(m--){ scanf("%d%d",&x,&y); if(loc[x][0]>loc[y][0])swap(x,y); if(loc[x][1]<loc[y][1])swap(x,y); if(loc[x][0]==loc[y][0]){ d[0][x]=y; if(!r[0][x])r[0][x]=y; }else{ r[0][x]=y; if(!d[0][x])d[0][x]=y; } } for(i=1;i<=n;i++){ if(!r[0][i])r[0][i]=n; if(!d[0][i])d[0][i]=n; } for(j=1;j<M;j++)for(i=1;i<=n;i++)d[j][i]=d[j-1][d[j-1][i]],r[j][i]=r[j-1][r[j-1][i]]; while(q--){ scanf("%d%d",&x,&y); puts(check(x,y)||check(y,x)?"TAK":"NIE"); } }
C. Will It Stop?
只有$2$的幂可行。
#include<cstdio> long long n; int main(){ scanf("%lld",&n); while(n%2==0)n/=2; puts(n>1?"NIE":"TAK"); }
D. Ants
$O(n)$模拟左边的蚂蚁,那么根据树的欧拉遍历总边数可以算出另一只蚂蚁为了走到这个点经过了多少步,继而求出第一次相遇的位置。
可以证明第二次相遇前,右边的蚂蚁一定回不到根,那么算第二次相遇的位置也是同理的。
E. Gophers
线段树线段覆盖,时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=500010,M=1222222; int n,m,q,lim,i,x,y,f[N],tag[M],v[M]; inline void up(int x,int a,int b){ if(tag[x])v[x]=b-a+1; else if(a==b)v[x]=0; else v[x]=v[x<<1]+v[x<<1|1]; } void change(int x,int a,int b,int c,int d,int p){ if(c<=f[a]&&f[b]<=d){ tag[x]+=p; up(x,a,b); return; } if(a==b)return; int mid=(a+b)>>1; if(c<=f[mid])change(x<<1,a,mid,c,d,p); if(d>=f[mid+1])change(x<<1|1,mid+1,b,c,d,p); up(x,a,b); } int main(){ scanf("%d%d%d%d",&n,&m,&q,&lim); for(i=2;i<=n;i++)scanf("%d",&f[i]); while(m--){ scanf("%d",&x); change(1,1,n,x-lim,x+lim,1); } printf("%d\n",v[1]); while(q--){ scanf("%d%d",&x,&y); change(1,1,n,x-lim,x+lim,-1); change(1,1,n,y-lim,y+lim,1); printf("%d\n",v[1]); } }
F. Laundry
按天数从大到小贪心选容量最小的颜色,set维护。
#include<cstdio> #include<set> #include<algorithm> using namespace std; typedef pair<int,int>P; int a[1111111],n,m,i,x,ans;set<P>T; int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=m;i++)scanf("%d",&x),T.insert(P(x,i)); sort(a+1,a+n+1); for(i=n;i;i--){ x=a[i]; set<P>::iterator it=T.lower_bound(P(x*5,0)); if(it!=T.end()){ ans++; T.erase(it); continue; } it=T.lower_bound(P(x*3,0)); if(it==T.end())return puts("NIE"),0; T.erase(it); ans++; it=T.lower_bound(P(x*2,0)); if(it==T.end())return puts("NIE"),0; T.erase(it); ans++; } printf("%d",ans); }
G. Bits Generator
每个$z$向计算一步后的$z'$连边建图,倍增求Hash值判断,时间复杂度$O(m\log n)$。
#include<cstdio> const int SEED=233,P=1000000007,N=1000010,LIM=17; int A,C,K,m,n,goal,i,j,ans; int f[LIM][N],g[LIM][N],pw[LIM]; char a[N]; inline int ask(int x,int m){ int ret=0; for(int i=0;i<LIM;i++)if(m>>i&1){ ret=(1LL*ret*pw[i]+g[i][x])%P; x=f[i][x]; } return ret; } int main(){ scanf("%d%d%d%d%d%s",&A,&C,&K,&m,&n,a+1); for(i=1;i<=n;i++)goal=(1LL*goal*SEED+a[i])%P; for(i=0;i<m;i++){ int x=(1LL*i*A+C)/K%m; int y=x<m/2?'0':'1'; f[0][i]=x; g[0][i]=y; } for(pw[0]=SEED,i=1;i<LIM;i++)pw[i]=1LL*pw[i-1]*pw[i-1]%P; for(j=1;j<LIM;j++)for(i=0;i<m;i++){ f[j][i]=f[j-1][f[j-1][i]]; g[j][i]=(1LL*g[j-1][i]*pw[j-1]+g[j-1][f[j-1][i]])%P; } for(i=0;i<m;i++)if(ask(i,n)==goal)ans++; printf("%d",ans); }
H. Afternoon Tea
在第$i(i<n)$个位置喝饮料可以看成贡献$1-\frac{1}{2^i}$,比较整数和小数部分即可。而小数部分除了$n=1$的情况外可以直接由第$n-1$次操作判断。
#include<cstdio> int n,i,x,b[2];char a[111111]; int cmp(){ if(b[0]!=b[1])return b[0]>b[1]?-1:1; if(n==1)return 0; return a[n-1]=='H'?1:-1; } int main(){ scanf("%d%s",&n,a+1); for(i=1;i<n;i++)b[a[i]=='H'?0:1]++; int ret=cmp(); if(ret==-1)puts("H"); if(ret==0)puts("HM"); if(ret==1)puts("M"); }
I. Intelligence Quotient
建立二分图,每个点向源/汇连边,容量为选他的收益,不认识的人之间连$inf$边求最小割即可。
#include<cstdio> typedef long long ll; const int N=810; const ll inf=1LL<<60; struct E{int t;ll f;E*nxt,*pair;}*g[N],*d[N],pool[500000],*cur=pool; int n,m,i,j,k,x,y,S,T,h[N],gap[N],A,B;ll ans; bool vis[N]; bool f[N][N]; inline void add(int s,int t,ll 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]; } inline ll min(ll a,ll b){return a<b?a:b;} ll sap(int v,ll flow){ if(v==T)return flow; ll rec=0; for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){ ll ret=sap(p->t,min(p->f,flow-rec)); 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; } void dfs(int x){ if(vis[x])return; vis[x]=1; for(E*p=g[x];p;p=p->nxt)if(p->f)dfs(p->t); } int main(){ scanf("%d%d%d",&n,&m,&k); S=n+m+1;T=S+1; while(k--)scanf("%d%d",&x,&y),f[x][y]=1; for(i=1;i<=n;i++)for(j=1;j<=m;j++)if(!f[i][j])add(i,j+n,inf); for(i=1;i<=n;i++)scanf("%d",&x),ans+=x,add(S,i,x); for(i=1;i<=m;i++)scanf("%d",&x),ans+=x,add(i+n,T,x); for(gap[0]=T,i=1;i<=T;i++)d[i]=g[i]; while(h[S]<T)ans-=sap(S,inf); printf("%lld\n",ans); dfs(S); for(i=1;i<=n;i++)if(vis[i])A++; printf("%d\n",A); for(i=1;i<=n;i++)if(vis[i])printf("%d ",i); puts(""); for(i=1;i<=m;i++)if(!vis[i+n])B++; printf("%d\n",B); for(i=1;i<=m;i++)if(!vis[i+n])printf("%d ",i); puts(""); }
J. Cave
枚举每个$k$,那么剩下每个子树大小均为$\frac{n}{k}$,因此$k$一定是$n$的因子,若此时还满足子树大小为它的倍数的点数够$k$个,则可行。
#include<cstdio> const int N=3000010; int n,i,x,y,g[N],v[N<<1],nxt[N<<1],ed; int size[N]; int cnt[N]; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ size[x]=1; for(int i=g[x];i;i=nxt[i])if(v[i]!=y)dfs(v[i],x),size[x]+=size[v[i]]; cnt[size[x]]++; } inline bool check(int x){ if(n%x)return 0; int w=n/x,t=0; for(int i=w;i<=n;i+=w)t+=cnt[i]; return t==x; } int main(){ scanf("%d",&n); for(i=2;i<=n;i++)x=i,scanf("%d",&y),add(x,y),add(y,x); dfs(1,0); for(i=1;i<=n;i++)if(check(i))printf("%d ",i); }
K. Cross Spider
特判共线的情况,得到$3$个不共线的点时叉积求出法向量判断。
#include<cstdio> typedef long long ll; struct P{ ll x,y,z; P(){} P(ll _x,ll _y,ll _z){x=_x,y=_y,z=_z;} P operator-(const P&b)const{return P(x-b.x,y-b.y,z-b.z);} P det(const P&b)const{ return P(y*b.z-z*b.y, z*b.x-x*b.z, x*b.y-y*b.x); } ll dot(const P&b)const{return x*b.x+y*b.y+z*b.z;} }A,B,C,F,O; int cnt,n; bool dotsInline(const P&A,const P&B,const P&C){ P D=(B-A).det(C-A); return !D.x&&!D.y&&!D.z; } int main(){ scanf("%d",&n); while(n--){ scanf("%lld%lld%lld",&O.x,&O.y,&O.z); if(!cnt)A=O,cnt=1; else if(cnt==1)B=O,cnt=2; else{ if(dotsInline(A,B,O))continue; if(cnt==2){ C=O,cnt=3; F=(B-A).det(C-A); continue; } if(F.dot(O-A))return puts("NIE"),0; } } puts("TAK"); }