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"); }