Potyczki Algorytmiczne 2009
Trial Round:
Rectangles
\[ans=\sum_{1\leq i\leq j\leq n}[ij\leq n]=\frac{\lfloor\sqrt{n}\rfloor+\sum_{1\leq i,j\leq n}[ij\leq n]}{2}=\frac{\lfloor\sqrt{n}\rfloor+\sum_{1\leq i\leq n}\lfloor\frac{n}{i}\rfloor}{2}\]
分$O(\sqrt{n})$段求和即可。
#include<cstdio> int n,ans,i,j; int main(){ scanf("%d",&n); for(i=1;i<=n;i=j+1)j=n/(n/i),ans+=(j-i+1)*(n/i); for(i=1;i*i<=n;i++)ans++; printf("%d",ans/2); }
Round 1:
Tables [B]
DP出$f[i]$表示$i$个箱子最多包含多少材料,答案即为最小的$i$满足$f[i]\geq ks$。
时间复杂度$O(n^2)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=1005; int n,k,s,i,j,x,f[N]; int main(){ scanf("%d%d%d",&n,&k,&s); for(i=1;i<=n;i++){ scanf("%d",&x); for(j=n;j;j--)f[j]=max(f[j],f[j-1]+x); } for(i=1;i<=n;i++)if(f[i]>=k*s)break; printf("%d",i); }
Round 2:
(K,N)-knight [B]
利用二维欧几里得算法求出能表示出原点能到的所有位置的两个基向量,然后尝试往其中加入向量$(\Delta x,\Delta y)$,若基没有变化则说明可以从$(0,0)$到达$(\Delta x,\Delta y)$。
时间复杂度$O(\log n)$。
#include<cstdio> typedef long long ll; int T; struct P{ ll x,y; P(){x=y=0;} P(ll _x,ll _y){x=_x,y=_y;} P operator*(ll b){return P(x*b,y*b);} P operator+(const P&b){return P(x+b.x,y+b.y);} }A,B,t; inline ll abs(ll x){return x>0?x:-x;} void exgcd(ll a,ll b,ll&x,ll&y){ if(!b){x=1;y=0;return;} exgcd(b,a%b,x,y); ll t=y;y=x-a/b*y;x=t; } inline ll gcd(ll a,ll b){ ll x,y; exgcd(a=abs(a),b=abs(b),x,y); return a*x+b*y; } inline ll lcm(ll a,ll b){return abs(a)/gcd(a,b)*abs(b);} inline void add(P C){ if(C.y<0)C=C*-1; if(!C.y)A.x=gcd(A.x,C.x); else{ if(B.y){ ll t=lcm(B.y,C.y); A.x=gcd(A.x,(t/B.y)*B.x-(t/C.y)*C.x); } ll a,b; exgcd(C.y,B.y,a,b); B=C*a+B*b; } if(A.x){ if(B.x<0)B.x+=(-B.x/A.x+1)*A.x; B.x-=B.x/A.x*A.x; } } inline bool solve(){ ll K,N,x1,y1,x2,y2; scanf("%lld%lld%lld%lld%lld%lld",&K,&N,&x1,&y1,&x2,&y2); ll dx=x2-x1,dy=y2-y1; if(!dx&&!dy)return 0; A=B=P(); add(P(N,K)); add(P(N,-K)); add(P(-N,K)); add(P(-N,-K)); add(P(K,N)); add(P(K,-N)); add(P(-K,N)); add(P(-K,-N)); ll pre=abs(A.x*B.y-A.y*B.x); add(P(dx,dy)); ll now=abs(A.x*B.y-A.y*B.x); return pre==now; } int main(){ for(scanf("%d",&T);T--;puts(solve()?"TAK":"NIE")); }
Dice [A]
由于输出的是0到1之间的小数概率,因此可以小范围暴力DP,大范围直接输出0。
#include<cstdio> typedef double ld; const int N=705; ld f[N+5][(N+5)*6]; int Case,n,m,i,j,k; inline int cal(int n,int m){ if(n>N||m>n*6)return 0; return f[n][m]; } int main(){ f[0][0]=100; for(i=0;i<=N;i++)for(j=0;j<=i*6;j++)for(k=1;k<=6;k++)f[i+1][j+k]+=f[i][j]/6; scanf("%d",&Case); while(Case--)scanf("%d%d",&n,&m),printf("%d\n",cal(n,m)); }
Round 3:
Pawn [B]
将每行从左往右分解成若干段极长的同色段,则一共有$O(n+m)$段。
对于位于相邻两行且互相接触的两个同色段,使用并查集将它们所在的连通块合并,即可知道两点是否可达。
时间复杂度$O((n+m+q)\log(n+m))$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100005,M=1000005,E=M*2+N; int n,m,q,cnt,i,j,k,x,y,A,B,L,R,st[N],en[N],f[E],l[E],r[E];char w[E]; struct P{int x,l,r;}e[M]; inline bool cmp(const P&a,const P&b){ if(a.x!=b.x)return a.x<b.x; return a.l<b.l; } inline void ext(int a,int b,int c){ if(a>b)return; l[++cnt]=a; r[cnt]=b; w[cnt]=c; } int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void merge(int x,int y){if(F(x)!=F(y))f[f[x]]=f[y];} inline int ask(int x,int y){ int a=st[x],b=en[x],mid; while(1){ mid=(a+b)>>1; if(r[mid]<y)a=mid+1; else if(l[mid]>y)b=mid-1; else return F(mid); } } int main(){ scanf("%d%d%d",&n,&m,&q); for(i=1;i<=m;i++)scanf("%d%d%d",&e[i].x,&e[i].l,&e[i].r); sort(e+1,e+m+1,cmp); for(i=j=1;i<=n;i++){ st[i]=cnt+1; L=1,R=0; while(j<=m&&e[j].x==i){ if(e[j].l>R+1){ ext(L,R,1); ext(R+1,e[j].l-1,0); L=e[j].l; } R=max(R,e[j].r); j++; } ext(L,R,1); ext(R+1,n,0); en[i]=cnt; } for(i=1;i<=cnt;i++)f[i]=i; for(i=1;i<n;i++)for(j=st[i],k=st[i+1];j<=en[i];j++){ while(k<=en[i+1]&&r[k]+1<l[j])k++; for(x=k;x<=en[i+1]&&l[x]-1<=r[j];x++)if(w[j]==w[x]&&F(j)!=F(x))f[f[j]]=f[x]; } while(q--){ scanf("%d%d",&x,&y); A=ask(x,y); scanf("%d%d",&x,&y); B=ask(x,y); puts(A==B?"TAK":"NIE"); } }
Drilling [A]
设$f[i][j]$表示仅考虑$[i,j]$区间的答案,则
$f[i][j]=\min(\max(f[i][k-1],f[k+1][j])+a[k])$,其中$i\leq k\leq j$。
根据$f$随着区间长度递增的性质,容易发现随着$k$的变大,$\max$的取值一定是先来自$f[k+1][j]$,再来自$f[i][k-1]$。
双指针出$\max$来源的分界线后,用$O(n)$个单调队列维护滑窗最小值即可。
时间复杂度$O(n^2)$。
#include<cstdio> const int N=2005,inf=~0U>>1; int n,i,j,a[N],g,f[N][N],h[N],t[N],q[N][N]; inline void up(int&a,int b){a>b?(a=b):0;} int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=n;i;i--){ f[i][i]=a[i]; q[0][h[0]=t[0]=h[i]=t[i]=1]=q[i][1]=i; for(g=i,j=i+1;j<=n;j++){ while(h[0]<=t[0]&&f[i][q[0][t[0]]-1]+a[q[0][t[0]]]>=f[i][j-1]+a[j])t[0]--; q[0][++t[0]]=j; while(h[j]<=t[j]&&f[q[j][t[j]]+1][j]+a[q[j][t[j]]]>=f[i+1][j]+a[i])t[j]--; q[j][++t[j]]=i; while(g<=j&&f[i][g-1]<f[g+1][j])g++; f[i][j]=inf; while(h[j]<=t[j]&&q[j][h[j]]>=g)h[j]++; if(h[j]<=t[j])up(f[i][j],f[q[j][h[j]]+1][j]+a[q[j][h[j]]]); while(h[0]<=t[0]&&q[0][h[0]]<g)h[0]++; if(h[0]<=t[0])up(f[i][j],f[i][q[0][h[0]]-1]+a[q[0][h[0]]]); } } printf("%d",f[1][n]); }
Round 4:
The Way to Bytemountain [B]
设$f[i][j]$表示从起点出发,一共看了不超过$i$次地图,最终走到$j$的最长路。
将状态按第一维$i$分层,不同层之间直接$O(m)$递推,同层之间利用$O(n)$的环套树递推实现转移。
时间复杂度$O(k(n+m))$。
#include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef long long ll; typedef pair<int,int>P; const int N=50005; const ll inf=1LL<<60; int n,m,cnt,K,i,j,x,y,z,g[N],w[N],d[N],q[N],h,t,pool[N],st[N],en[N],s[N],all[N]; bool vis[N]; ll pre[N],now[N],cur[N]; vector<P>e[N]; inline void up(ll&a,ll b){a<b?(a=b):0;} inline void gao(){ int i,j,l,r;ll tmp; for(i=1;i<=t;i++)up(now[g[q[i]]],now[q[i]]+w[q[i]]); for(i=1;i<=m;i++){ l=st[i],r=en[i]; for(j=l;j<=r;j++)cur[j]=-inf; for(tmp=-inf,j=l;j<=r;j++){ up(cur[j],tmp+s[j]); up(tmp,now[pool[j]]-s[j]); } for(tmp=-inf,j=r;j>=l;j--){ up(cur[j],tmp+s[j]); up(tmp,now[pool[j]]-s[j]+all[i]); } for(j=l;j<=r;j++)up(now[pool[j]],cur[j]); } } int main(){ scanf("%d%d",&n,&K); for(i=1;i<=n;i++){ scanf("%d",&z); e[i].resize(z); for(j=0;j<z;j++){ scanf("%d%d",&x,&y); e[i][j]=P(x,y); } g[i]=e[i][0].first; w[i]=e[i][0].second; d[g[i]]++; } for(h=i=1;i<=n;i++)if(!d[i])q[++t]=i; while(h<=t)if(!(--d[y=g[q[h++]]]))q[++t]=y; for(i=1;i<=t;i++)vis[q[i]]=1; for(i=1;i<=n;i++)if(!vis[i]){ pool[++cnt]=i; st[++m]=cnt; for(x=g[i];x!=i;x=g[x])pool[++cnt]=x; en[m]=cnt; for(j=st[m];j<=en[m];j++)vis[pool[j]]=1; for(j=st[m];j<en[m];j++)s[j+1]=s[j]+w[pool[j]]; all[m]=s[en[m]]+w[pool[en[m]]]; } for(i=2;i<=n;i++)now[i]=-inf; gao(); while(K--){ for(i=1;i<=n;i++)pre[i]=now[i]; for(i=1;i<=n;i++)for(j=0;j<e[i].size();j++)up(now[e[i][j].first],pre[i]+e[i][j].second); gao(); } printf("%lld",now[n]); }
Stamps [A]
如果$3\times 3$印章里两个$1$在同一列,则把所有矩阵旋转$90$度,那么最终小印章只有两种情况:
$11$:
- 即判断能否通过大印章把目标矩阵每行的异或和都搞成$0$。
- 对于每行,列出一个方程,大印章只需考虑放在最左侧的情况,共$k-s+1$个未知量。
$101$:
- 即判断能否通过大印章把目标矩阵搞成每行奇数位置的异或和都是$0$且每行偶数位置的异或和都是$0$。
- 对于每行,列出两个方程,大印章只需考虑放在最左侧或者离最左侧一个位置的两种情况,最多$2(k-s+1)$个未知量。
列出异或方程后,bitset加速高斯消元即可,时间复杂度$O(\frac{k^3}{w})$。
#include<cstdio> #include<bitset> #include<algorithm> using namespace std; const int N=1005,M=N*2; int Case,n,m,i,w[N],v[N];char a[9][9],b[N][N],c[N][N],g[M][M];bitset<M>f[M]; inline bool gauss(int n,int m){ int i,j,k; for(i=1;i<=m;i++)for(j=0;j<=n;j++)f[i][j]=g[i][j]; for(i=j=1;i<=n;i++){ for(k=j;k<=m;k++)if(f[k][i])break; if(k>m)continue; swap(f[j],f[k]); for(k=j+1;k<=m;k++)if(f[k][i])f[k]^=f[j]; j++; } for(i=1;i<=m;i++)if(f[i][0]){ for(j=1;j<=n;j++)if(f[i][j])break; if(j>n)return 0; } return 1; } inline bool check11(){ int i,j,cnt=m-n+1; for(i=1;i<=n;i++)for(w[i]=0,j=1;j<=n;j++)if(b[i][j]=='1')w[i]^=1; for(i=1;i<=m;i++){ for(j=0;j<=cnt;j++)g[i][j]=0; for(j=1;j<=m;j++)if(c[i][j]=='1')g[i][0]^=1; for(j=1;j<=cnt;j++)if(j<=i&&j+n-1>=i)g[i][j]=w[i-j+1]; } return gauss(cnt,m); } inline bool check101(){ int i,j,cnt=m-n+1,all=n<m?cnt*2:cnt; for(i=1;i<=n;i++){ for(w[i]=0,j=1;j<=n;j+=2)if(b[i][j]=='1')w[i]^=1; for(v[i]=0,j=2;j<=n;j+=2)if(b[i][j]=='1')v[i]^=1; } for(i=1;i<=m;i++){ for(j=0;j<=all;j++)g[i][j]=0; for(j=1;j<=m;j+=2)if(c[i][j]=='1')g[i][0]^=1; for(j=1;j<=cnt;j++)if(j<=i&&j+n-1>=i){ g[i][j]=w[i-j+1]; if(n<m)g[i][j+cnt]=v[i-j+1]; } for(j=0;j<=all;j++)g[i+m][j]=0; for(j=2;j<=m;j+=2)if(c[i][j]=='1')g[i+m][0]^=1; for(j=1;j<=cnt;j++)if(j<=i&&j+n-1>=i){ g[i+m][j]=v[i-j+1]; if(n<m)g[i+m][j+cnt]=w[i-j+1]; } } return gauss(all,m+m); } inline void rotate(char a[][N],int n){ int i,j; static char b[N][N]; for(i=1;i<=n;i++)for(j=1;j<=n;j++)b[j][i]=a[i][j]; for(i=1;i<=n;i++)for(j=1;j<=n;j++)a[i][j]=b[i][j]; } inline bool check(){ int i,j; if(m==1)return c[1][1]=='0'||c[1][1]==b[1][1]; for(i=1;i<=3;i++)for(j=1;j<=3;j++)if(a[i][j]=='1'){ if(a[i][j+1]=='1')return check11(); if(a[i][j+2]=='1')return check101(); if(a[i+1][j]=='1'){ rotate(b,n); rotate(c,m); return check11(); } if(a[i+2][j]=='1'){ rotate(b,n); rotate(c,m); return check101(); } } } int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); for(i=1;i<=3;i++)scanf("%s",a[i]+1); for(i=1;i<=n;i++)scanf("%s",b[i]+1); for(i=1;i<=m;i++)scanf("%s",c[i]+1); puts(check()?"TAK":"NIE"); } }
Round 5:
Byteantean Towns [B]
枚举直线$L$,把所有直线按照与$L$的交点沿直线方向排序。
那么对于其中第$i$个交点,它对应的直线左侧和右侧在$L$上的交点数都知道了,分别是$i-1$以及交点数$-i$。
如此一来对于每条直线$L$,两条均不与$L$平行的直线的交点会被上述方法统计两次,但是某条与$L$平行的直线和某条不与$L$平行的直线的交点只会被统计一次,因此令$f[L]$表示直线$L$上的交点数,枚举与$L$平行的所有直线,将对应答案加上$f[L]$即可,最后再将所有答案除以$2$。
时间复杂度$O(n^2\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef double ld; const int N=1005; int n,m,i,j,ans[N],q[N],v[N];ld w[N]; struct P{ int x,y; P(){} P(int _x,int _y){x=_x,y=_y;} P operator-(const P&p)const{return P(x-p.x,y-p.y);} }l[N][2]; struct E{ld x,y;}e[N]; inline bool cmp(int x,int y){return w[x]<w[y];} inline int cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;} inline bool line_intersection(const P&a,const P&b,const P&p,const P&q,E&o,ld&t){ int U=cross(p-a,q-p),D=cross(b-a,q-p); if(!D)return 0; t=((ld)U)/((ld)D); o.x=a.x+(b.x-a.x)*t; o.y=a.y+(b.y-a.y)*t; return 1; } inline int ptoline(const P&a,const P&b,const P&c){ int t=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); return t>0?1:-1; } inline int ptoline(const P&a,const P&b,const E&c){ ld t=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); return t>0?1:-1; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)for(j=0;j<2;j++)scanf("%d%d",&l[i][j].x,&l[i][j].y); for(i=1;i<=n;i++){ m=0; for(j=1;j<=n;j++)if(j!=i)if(line_intersection(l[i][0],l[i][1],l[j][0],l[j][1],e[j],w[j]))q[++m]=j; for(j=1;j<=n;j++)v[j]=0; v[i]=1; for(j=1;j<=m;j++)v[q[j]]=1; for(j=1;j<=n;j++)if(!v[j])ans[j]-=ptoline(l[j][0],l[j][1],l[i][0])*m; if(m<=1)continue; sort(q+1,q+m+1,cmp); for(j=1;j<=m;j++){ if(j>1)ans[q[j]]+=ptoline(l[q[j]][0],l[q[j]][1],e[q[j-1]])*(m-j-j+1); else ans[q[j]]-=ptoline(l[q[j]][0],l[q[j]][1],e[q[j+1]])*(m-1); } } for(i=1;i<=n;i++)printf("%d\n",(ans[i]>0?ans[i]:-ans[i])/2); }
Circular Game [A]
令先手为$A$,后手为$B$,将相邻同色棋子合并成块,首先特判一些情况:
- 如果所有格子都是满的,那么显然$A$必败。
- 否则如果所有块都只有一个棋子,那么显然平局。
- 枚举$A$的第一步操作,如果可以使得B无法操作,那么显然$A$必胜。
无视所有大小为$1$的块,考虑剩下块里相邻两块,它们往外扩张比往内缩更优:
- 如果是形如[A_A]___B__A__BA____B___[AA_A],两边同色,所以中间这些块(包括位置)可以删除,不影响游戏结果。
- 如果是形如[A_A]___B__A__B_A__[BB_B],两边异色,那么相邻两块相互靠近更优,可以等效替代成[A_A]___[BB][AA]__[BB][AA]__[BB_B]。
经过上述转化后,不再存在大小为$1$的块,当存在大小至少为$3$且内部存在空隙的自由块时,该块显然可以永远操作下去。假设不存在自由块,那么显然不会平局,游戏等价于每堆石子数为相邻两块间距的Nim游戏。
分以下情况讨论:
- $A$可以通过至多一步操作得到自由块,且可以阻止$B$得到自由块,此时结果为$A$胜。
- $AB$一开始都没有自由块,但都可以通过一步得到,$A$先堵$B$,$B$再堵$A$后双方都没有自由块,但是此时Nim游戏$A$胜,则最终$A$胜。
- $AB$都没法通过一步操作得到自由块,但是此时Nim游戏$A$胜,则最终$A$胜。
- $A$可以通过至多一步操作得到自由块,但是阻止不了$B$得到自由块,此时结果为平局。
- $A$无法得到自由块,也阻止不了$B$得到自由块,此时结果为$B$胜。
- 双方都没法得到自由块,但是此时Nim游戏$B$胜,则最终$B$胜。
时间复杂度$O(B+C)$。
#include<cstdio> #include<cstring> #define CLR(x) memset(x,0,sizeof x) const int N=1000010,BUF=12000000; char Buf[BUF],*buf=Buf; int Case,m,n,ce,A,B,i,j,k,o,x,st,a[N],b[N],can10,can11,can01,can00; struct P{ int x;char col; P(){} P(int _x,char _col){x=_x,col=_col;} }q[N]; struct E{ int len,cnt,dis;char col; E(){} E(int _len,int _cnt,int _dis,char _col){len=_len,cnt=_cnt,dis=_dis,col=_col;} bool free(){return len>cnt&&cnt>=3;} }e[N]; int cfree[2],cmove[2],cnt[2],cmay[2],sum[2],CFREE,CMAY0,CMAY1; inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} inline void cal(){ if(e[0].col==e[ce-1].col){ ce--; e[0].len+=e[ce].len+e[ce].dis; e[0].cnt+=e[ce].cnt; } CLR(cfree),CLR(cmove),CLR(cnt),CLR(cmay);CLR(sum); for(i=0;i<ce;i++){ cnt[e[i].col]++; if(e[i].free())cfree[e[i].col]++,cmove[e[i].col]++,cmay[e[i].col]++; if(e[i].len>e[i].cnt)cmove[e[i].col]++; if(e[i].dis){ cmove[e[i].col]++; if(e[i].cnt>=3){ cmay[e[i].col]++; sum[e[i].col]^=e[i].dis; } } if(e[(i-1+ce)%ce].dis){ cmove[e[i].col]++; if(e[i].cnt>=3){ cmay[e[i].col]++; sum[e[i].col]^=e[(i-1+ce)%ce].dis; } } } } inline bool can_block_b(){ if(cfree[1])return 0; if(cmove[1]>1)return 0; for(i=0;i<ce;i++)if(e[i].col==1&&e[i].len>e[i].cnt)return 0; for(i=0;i<ce;i++)if(e[i].col==1){ if(e[i].dis&&e[(i+1)%ce].len>1)return 1; if(e[(i-1+ce)%ce].dis&&e[(i-1+ce)%ce].len>1)return 1; } return 0; } inline void change(int x,int y){ CFREE=cfree[0],CMAY0=cmay[0],CMAY1=cmay[1]; if(e[x].cnt>=3)CFREE++,CMAY0++; if(e[y].cnt>=3&&!e[y].free())CMAY1--; } inline int solve(){ read(m),read(A),read(B); for(i=1;i<=A;i++)read(a[i]); for(i=1;i<=B;i++)read(b[i]); //Circle is full, so A can't move if(A+B==m)return -1; n=0; i=j=1; while(i<=A&&j<=B)if(a[i]<b[j])q[++n]=P(a[i++],0);else q[++n]=P(b[j++],1); while(i<=A)q[++n]=P(a[i++],0); while(j<=B)q[++n]=P(b[j++],1); ce=0; for(i=1;i<=n;i=j+1){ for(j=i;j<n&&q[i].col==q[j+1].col;j++); e[ce++]=E(q[j].x-q[i].x+1,j-i+1,q[j+1].x-q[j].x-1,q[i].col); } e[ce-1].dis=q[1].x-q[n].x-1+m; cal(); //All blocks' length is 1, draw for(i=0;i<ce;i++)if(e[i].len!=1)break; if(i>=ce)return 0; //A can't move if(!cmove[0])return -1; //A can make B not to move if(can_block_b())return 1; for(i=0;i<ce;i++)if(e[i].len>1){st=i;break;} for(i=0;i<ce;i=j){ for(j=i+1;j<=ce;j++)if(e[(st+j)%ce].len>1)break; int l=(st+i)%ce; if(e[l].col!=e[(st+j)%ce].col)for(k=i+1,o=1;k<j;k++,o^=1){ x=(st+k)%ce; if(o)e[x].dis=0; e[x].len=e[x].cnt=2; }else{ e[l].len+=e[l].dis; e[l].dis=0; for(k=i+1,o=1;k<j;k++,o^=1){ x=(st+k)%ce; if(o)cnt[e[x].col]--; e[l].len+=e[x].len+e[x].dis; e[l].cnt+=e[x].cnt; } } } //No such blocks if(!cnt[0])return -1; if(!cnt[1])return 1; for(i=n=0;i<ce;i++)if(e[i].len>1)e[n++]=e[i]; for(ce=i=1;i<n;i++)if(e[i].col==e[ce-1].col){ e[ce-1].len+=e[i].len+e[ce-1].dis; e[ce-1].cnt+=e[i].cnt; e[ce-1].dis=e[i].dis; }else e[ce++]=e[i]; cal(); //A can't move if(!cmove[0])return -1; //A can make B not to move if(can_block_b())return 1; can10=can11=can01=can00=0; CFREE=cfree[0],CMAY0=cmay[0],CMAY1=cmay[1]; if(CFREE){ if(!CMAY1)can10=1; if(cfree[1])can11=1; } for(x=i=0;i<ce;i++)x^=e[i].dis; for(i=0;i<ce;i++)if(e[i].col==0){ if(e[i].dis){ change(i,(i+1)%ce); if((CMAY0>1||CFREE)&&!CMAY1)can10=1; if(CFREE&&CMAY1)can11=1; if(!CFREE&&CMAY1)can01=1; if(!CMAY1&&x==e[i].dis)can00=1; if(!CFREE&&CMAY0==1&&!CMAY1)if(x^e[i].dis^sum[0])can00=1; } if(e[(i-1+ce)%ce].dis){ change(i,(i-1+ce)%ce); if((CMAY0>1||CFREE)&&!CMAY1)can10=1; if(CFREE&&CMAY1)can11=1; if(!CFREE&&CMAY1)can01=1; if(!CMAY1&&x==e[(i-1+ce)%ce].dis)can00=1; if(!CFREE&&CMAY0==1&&!CMAY1)if(x^e[(i-1+ce)%ce].dis^sum[0])can00=1; } } if(can10)return 1; if(can00)return 1; if(can11)return 0; if(can01)return -1; return x?1:-1; } int main(){ fread(Buf,1,BUF,stdin);read(Case); while(Case--){ int x=solve(); if(x>0)puts("B"); if(!x)puts("R"); if(x<0)puts("C"); } return 0; }
Permutation [B]
根据Hall定理,有解当且仅当对于任意的$k$都满足$p\leq k$的数字的个数不超过$k$。
令$f[k]$表示$p=k$的数字个数$-1$,线段树维护$f$的最大前缀和,判断是否非正。
时间复杂度$O(m\log n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=200005,M=524305; int n,m,i,x,y,a[N],f[N],v[M],s[M],pos[N]; inline void up(int x){ v[x]=max(v[x<<1],s[x<<1]+v[x<<1|1]); s[x]=s[x<<1]+s[x<<1|1]; } void build(int x,int a,int b){ if(a==b){ pos[a]=x; s[x]=v[x]=f[a]; return; } int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b),up(x); } inline void change(int x){ int y=pos[x]; s[y]=v[y]=f[x]; for(y>>=1;y;y>>=1)up(y); } inline void ask(){puts(v[1]<=0?"TAK":"NIE");} int main(){ scanf("%d",&n); for(i=1;i<=n;i++)f[i]=-1; for(i=1;i<=n;i++)scanf("%d",&a[i]),f[a[i]]++; build(1,1,n); ask(); scanf("%d",&m); while(m--){ scanf("%d%d",&x,&y); f[a[x]]--; change(a[x]); f[a[x]=y]++; change(y); ask(); } }
Quasi-template [A]
建立后缀树,用线段树合并求出每个节点子树内部最靠前和最靠后的后缀位置以及相邻后缀距离的最大值,同时求出每个子串能完整匹配的最长后缀的长度。
对于一个子串,如果其长度不小于相邻后缀距离的最大值,且最靠后的位置加上最长匹配的后缀长度不小于$n$,那么就说明可以从中间开始覆盖到尾部。
对串做KMP,求出每个前缀的最长border,也就是$nxt$数组。
对于一个子串,设其最早出现的位置为$[l,r]$,那么只要$nxt[r]\geq l-1$,就说明可以覆盖头部。
因为后缀树边经过压缩,所以不能直接枚举所有子串。
对于计数,可以离线之后扫描线树状数组处理。
对于长度最小且字典序最小的解,可以考虑用线段树维护区间内$nxt$的最大值,然后在线段树上二分。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; const int inf=1<<30,S=27,N=200010; int root,last,pos,need,remain,acnode,ace,aclen; int n,i,j,nxt[N],f[N<<1],q[N],cnt,first,minl=inf,ansl,ansr; char text[N],a[N];long long ans; struct node{int st,en,lk,son[S];int len(){return min(en,pos+1)-st;}}tree[N<<1]; struct E{int x,l,r;E(){}E(int _x,int _l,int _r){x=_x,l=_l,r=_r;}}e[N<<1]; inline bool cmp(int x,int y){return nxt[x]>nxt[y];} inline bool cmpe(const E&a,const E&b){return a.x>b.x;} namespace DS{ const int M=3800000; int tot,l[M],r[M],v[M],vl[M],vr[M],T[N<<1]; int build(int a,int b,int c){ int x=++tot; vl[x]=vr[x]=c; if(a==b)return tot; int mid=(a+b)>>1; if(c<=mid)l[x]=build(a,mid,c);else r[x]=build(mid+1,b,c); return x; } int merge(int x,int y,int a,int b){ if(!x||!y)return x+y; int mid=(a+b)>>1; l[x]=merge(l[x],l[y],a,mid); r[x]=merge(r[x],r[y],mid+1,b); vl[x]=l[x]?vl[l[x]]:vl[r[x]]; vr[x]=r[x]?vr[r[x]]:vr[l[x]]; v[x]=max(v[l[x]],v[r[x]]); if(l[x]&&r[x])v[x]=max(v[x],vl[r[x]]-vr[l[x]]); return x; } } namespace RangeQuery{ int v[524300],bit[N]; void build(int x,int a,int b){ if(a==b){ v[x]=nxt[a]; return; } int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); v[x]=max(v[x<<1],v[x<<1|1]); } void get(int x,int a,int b,int c,int d,int p){ if(first<inf||v[x]<p)return; if(a==b)first=a; int mid=(a+b)>>1; if(c<=mid)get(x<<1,a,mid,c,d,p); if(d>mid)get(x<<1|1,mid+1,b,c,d,p); } inline void add(int x){for(x++;x<=n;x+=x&-x)bit[x]++;} inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=bit[x];return t;} inline int sum(int l,int r){return ask(r+1)-ask(l);} } int new_node(int st,int en=inf){ node nd; nd.st=st;nd.en=en; for(int i=nd.lk=0;i<S;i++)nd.son[i]=0; tree[++last]=nd; return last; } char acedge(){return text[ace];} void addedge(int node){ if(need)tree[need].lk=node; need=node; } bool down(int node){ if(aclen>=tree[node].len())return ace+=tree[node].len(),aclen-=tree[node].len(),acnode=node,1; return 0; } void init(){ need=last=remain=ace=aclen=0; root=acnode=new_node(pos=-1,-1); } void extend(char c){ text[++pos]=c;need=0;remain++; while(remain){ if(!aclen)ace=pos; if(!tree[acnode].son[acedge()])tree[acnode].son[acedge()]=new_node(pos),addedge(acnode); else{ int nxt=tree[acnode].son[acedge()]; if(down(nxt))continue; if(text[tree[nxt].st+aclen]==c){aclen++;addedge(acnode);break;} int split=new_node(tree[nxt].st,tree[nxt].st+aclen); tree[acnode].son[acedge()]=split; tree[split].son[c]=new_node(pos); tree[nxt].st+=aclen; tree[split].son[text[tree[nxt].st]]=nxt; addedge(split); } remain--; if(acnode==root&&aclen)aclen--,ace=pos-remain+1; else acnode=tree[acnode].lk?tree[acnode].lk:root; } } void dfs(int x,int y,int sum){ sum+=tree[x].len(); if(sum&&min(tree[x].en,pos+1)==pos+1){ if(!tree[x].len())f[y]=max(f[y],sum); else f[x]=sum; } for(int i=0;i<S;i++){ int u=tree[x].son[i]; if(!u)continue; dfs(u,x,sum); } } inline void solve(int l,int r,int x,int y,int ml){ l=max(l,max(DS::v[DS::T[x]],n-ml-DS::vr[DS::T[x]])); if(l>r)return; x=DS::vl[DS::T[x]]; if(l+10>r){ for(int i=l;i<=r;i++)if(nxt[y+i-1]>=x){ ans++; if(i<minl)minl=i,ansl=y,ansr=y+i-1; } return; } first=inf; RangeQuery::get(1,0,n,y+l-1,y+r-1,x); if(first==inf)return; first-=y-1; if(first<minl)minl=first,ansl=y,ansr=y+first-1; e[cnt++]=E(x,y+l-1,y+r-1); } void dfs2(int x,int y,int sum){ int l=sum+1; sum+=tree[x].len(); if(sum&&min(tree[x].en,pos+1)==pos+1)DS::T[x]=DS::build(0,n,pos-sum+1); for(int i=0;i<S;i++){ int u=tree[x].son[i]; if(!u)continue; f[u]=max(f[u],f[x]); dfs2(u,x,sum); DS::T[x]=DS::merge(DS::T[x],DS::T[u],0,n); } if(l<=sum){ solve(l,sum-1,x,tree[x].st-l+1,f[y]); solve(sum,sum,x,tree[x].st-l+1,f[x]); } } int main(){ init(); scanf("%s",a); n=strlen(a); for(nxt[0]=j=-1,i=1;i<n;nxt[i++]=j){ while(~j&&a[j+1]!=a[i])j=nxt[j]; if(a[j+1]==a[i])j++; } for(i=0;i<n;i++)nxt[i]++,q[i]=i; RangeQuery::build(1,0,n); for(i=0;i<n;i++)extend(a[i]-'a');extend(26); pos--; dfs(root,0,0); dfs2(root,0,0); sort(q,q+n,cmp); if(cnt>1)sort(e,e+cnt,cmpe); for(i=j=0;i<cnt;i++){ while(j<n&&nxt[q[j]]>=e[i].x)RangeQuery::add(q[j++]); ans+=RangeQuery::sum(e[i].l,e[i].r); } printf("%lld\n",ans); for(i=ansl;i<=ansr;i++)putchar(a[i]); return 0; }
Round 6:
Cakes [A]
设$d_x$表示$x$点的度数,称点$x$比点$y$小当且仅当$d_x<d_y$或$d_x=d_y$且$x<y$。
对于每条无向边$(u,v)$,将其定向为单向边,由小点连向大点,那么每个点的出度不超过$O(\sqrt{m})$。
枚举点$u$,然后枚举$u$的所有出边$u\rightarrow v$,将$v$标记为“与$u$相连”。标记完毕后,继续枚举$u$的每条出边$u\rightarrow v$,然后枚举$v$的每条出边$v\rightarrow w$,若$w$被标记为“与$u$相连”,则找到了一个三元环$(u,v,w)$。
时间复杂度$O(m\sqrt{m})$。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; const int N=100005,M=250005; int n,m,i,j,k,x,y,p[N],e[M][2],d[N],v[N];vector<int>g[N];long long ans; int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&p[i]); for(i=1;i<=m;i++){ scanf("%d%d",&x,&y); e[i][0]=x,e[i][1]=y; d[x]++,d[y]++; } for(i=1;i<=m;i++){ x=e[i][0],y=e[i][1]; if(d[x]>d[y]||d[x]==d[y]&&x>y)swap(x,y); g[x].push_back(y); } for(i=1;i<=n;i++){ for(j=0;j<g[i].size();j++)v[g[i][j]]=i; for(j=0;j<g[i].size();j++){ x=g[i][j]; for(k=0;k<g[x].size();k++){ y=g[x][k]; if(v[y]==i)ans+=max(p[i],max(p[x],p[y])); } } } printf("%lld",ans); }
Diamond [B]
首先$O(n^4)$求出三维凸包上的所有$O(n)$个面,即$O(n^3)$枚举三个点$A,B,C$,若所有点都在平面$ABC$的同侧,那么$ABC$是一个凸包面。
然后枚举三个点$A,B,C$,同时$2^3$枚举平面$ABC$应该如何微移,使得每个点都不在平面上,那么一个面被该平面切割到当且仅当三个点分别在平面的两侧。
计算出平面切割到的凸包面数的最大值$k$后,答案就是$k+2+$凸包面数。
时间复杂度$O(n^4)$。
#include<cstdio> const int N=85,inf=~0U>>1; int n,m,i,j,k,S,o,tmp,ans,f[N<<1][3],l,r,v[N]; struct P{ int x,y,z; P(){} P(int _x,int _y,int _z){x=_x,y=_y,z=_z;} P operator-(const P&p)const{return P(x-p.x,y-p.y,z-p.z);} P operator*(const P&p)const{return P(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);} int operator^(const P&p)const{return x*p.x+y*p.y+z*p.z;} }p[N]; inline int ptoplane(const P&a,const P&b,const P&c,const P&p){return((b-a)*(c-a))^(p-a);} inline void umin(int&a,int b){a>b?(a=b):0;} inline void umax(int&a,int b){a<b?(a=b):0;} int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z); for(i=1;i<=n;i++)for(j=1;j<i;j++)for(k=1;k<j;k++){ l=inf,r=-inf; for(o=1;o<=n;o++){ tmp=ptoplane(p[i],p[j],p[k],p[o]); umin(l,tmp); umax(r,tmp); } if(l<0&&r>0)continue; f[++m][0]=i; f[m][1]=j; f[m][2]=k; } for(i=1;i<=n;i++)for(j=1;j<i;j++)for(k=1;k<j;k++){ for(o=1;o<=n;o++)v[o]=ptoplane(p[i],p[j],p[k],p[o])>0; for(v[i]=0;v[i]<2;v[i]++)for(v[j]=0;v[j]<2;v[j]++)for(v[k]=0;v[k]<2;v[k]++){ tmp=0; for(o=1;o<=m;o++){ S=v[f[o][0]]+v[f[o][1]]+v[f[o][2]]; if(S==1||S==2)tmp++; } umax(ans,tmp); } } printf("%d",ans+m+2); }
Cards [B]
如果第一个字符是'B',或者某个连续'B'子串$T$前面跟着严格小于$k|T|$个字符'R',那么Alice胜利。
因此Alice输当且仅当第一个字符是'R'且任意连续'B'子串$T$前面跟着至少$k|T|$个'R',用总方案数减去Alice输的方案数即可得到答案,其中Alice输的方案一定形如"R..RB..B"循环或者"R..RB..B"循环并且最后还跟着一段'R'。
从左往右填字符,令横坐标$x$表示已有的'R'的数量,$y$坐标表示已有的'B'的数量,则要从$(0,0)$走到$(1..n,m)$,从$(x,y)$可以一次性走到$(x+\Delta x,y+\Delta y)$当且仅当$\Delta x>0$, $\Delta y>0$, 且$\Delta x\geq k\Delta y$。最后再补足'R'到$(n,m)$,即不断叠加向量$(1,0)$。
这等价于每次从$(x,y)$可以走到$(x+1,y)$或者走到$(x+k,y+1)$,从$(0,0)$到$(n,m)$的全过程中向量$(k,1)$共使用$m$次,向量$(1,0)$共使用$n-m\times k$次,且满足每次的第一个向量一定是$(k,1)$。
也就是把这些$(k,1)$分组,每组后面插入共$n-m\times k$个$(1,0)$,其中每组$(1,0)$数量可以为$0$,且除了最后一组之外每一组的$(k,1)$数量都不能为$0$。
于是枚举分组的数量,然后Lucas求组合数即可。
时间复杂度$O((n+m)\log P)$。
#include<cstdio> const int N=200005; int n,m,k,P,lim,i,fac[N],inv[N],ans; int C(int a,int b){ if(a<b)return 0; return 1LL*fac[a]*inv[b]%P*inv[a-b]%P; } int lucas(int a,int b){ if(a<b)return 0; if(a==b||b==0)return 1; return 1LL*C(a%P,b%P)*lucas(a/P,b/P)%P; } int main(){ scanf("%d%d%d%d",&n,&m,&k,&P); lim=n+m; if(lim>=P)lim=P-1; for(fac[0]=i=1;i<=lim;i++)fac[i]=1LL*fac[i-1]*i%P; for(inv[0]=inv[1]=1,i=2;i<=lim;i++)inv[i]=1LL*(P-P/i)*inv[P%i]%P; for(i=2;i<=lim;i++)inv[i]=1LL*inv[i-1]*inv[i]%P; ans=lucas(n+m,m); if(n-m*k>=0)for(i=1;i<=m;i++)ans=(ans-1LL*lucas(n-m*k+i,i)*lucas(m-1,i-1)%P+P)%P; printf("%d",ans); }
Fishes [A]
令$f[i][j]$表示$(i,j)$往上碰到的第一个障碍的编号:
- 如果鱼的路线从左往右从$(i,j-1)$走到了$(i,j)$,那么记录下$f[i][j]$。
- 如果鱼的路线从右往左从$(i,j+1)$走到了$(i,j)$,那么记录下$-f[i][j+1]$。
如此一来每条鱼的路线都可以表示成一个循环的序列,并且序列中相邻互为相反数的项可以抵消。
整理出抵消后最简洁的序列后,两条鱼相同当且仅当它们的序列循环同构。
利用Hash求出每条鱼对应序列的所有循环同构中Hash值的最大值,那么两条鱼相同当且仅当它们的Hash值最大值相同。
时间复杂度$O(wh+nd)$。
#include<cstdio> #include<algorithm> using namespace std; typedef unsigned long long ull; const int N=1005,M=10005,S=1000000007; int w,h,tot,ans,i,j,n,f[N][N],q[N],who[N],e[M];char s[M];ull v[N]; inline bool cmp(int x,int y){return v[x]==v[y]?x<y:v[x]<v[y];} inline ull cal(){ int r,c,len,i,j,h=1,t=0; scanf("%d%d%d%s",&c,&r,&len,s+1); for(i=1;i<=len;i++){ if(s[i]=='N')r--; if(s[i]=='S')r++; if(s[i]=='W')e[++t]=f[r][c--]; if(s[i]=='E')e[++t]=-f[r][++c]; while(t>1&&e[t-1]+e[t]==0)t-=2; } while(h<t&&e[h]+e[t]==0)h++,t--; ull ret,base=1,now=0; for(i=h;i<=t;i++)base*=S,now=now*S+e[i]; ret=now; for(i=h;i<=t;i++){ now=now*S+e[i]-base*e[i]; if(ret<now)ret=now; } return ret; } int main(){ scanf("%d%d",&w,&h); for(j=1;j<=w;j++)f[0][j]=++tot; for(i=1;i<=h;i++){ scanf("%s",s+1); for(j=1;j<=w;j++)if(s[j]=='.')f[i][j]=f[i-1][j];else f[i][j]=++tot; } scanf("%d",&n); for(i=1;i<=n;i++)v[i]=cal(),q[i]=i; sort(q+1,q+n+1,cmp); for(i=1;i<=n;i=j){ for(j=i;j<=n&&v[q[i]]==v[q[j]];j++)who[q[j]]=q[i]; ans++; } printf("%d\n",ans); for(i=1;i<=n;i++){ for(ans=0,j=1;j<=n;j++)if(who[j]==i)ans=1,printf("%d ",j); if(ans)puts(""); } }
Trial Finals:
Task Chess
令$f[n]$表示$n$的答案,显然$f[0]=f[1]=1$。
对于$n\geq 2$的情况,枚举第一行的车在哪个位置,那么它和转$180$度的位置不能冲突,有$n-2-n\bmod 2$种方案,所以$f[n]=f[n-4]\times (n-2-n\bmod 2)$。
即要求$O(n)$个int的高精度乘积,利用分治FFT在$O(n\log^2n)$的时间里求出答案。
#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<cstdlib> #include<vector> using namespace std; typedef long long ll; const int K=16,MAXL=16000,LEN=70005; char s[LEN]; namespace FFT{ const int N=16387,B=10000; typedef long double DB; const DB pi=acos(-1.0); char s[LEN],t[LEN];int i,j,l,pos[N];ll ans[N]; struct comp{ DB r,i;comp(DB _r=0,DB _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]; 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; DB 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; } void work(){ int m=max(strlen(s),strlen(t))/4+10,N=1; while(N<m)N<<=1; N<<=1; for(i=0;i<N;i++)a[i]=b[i]=comp(),ans[i]=0; for(i=0,j=strlen(s);i<j;i++)a[(j-i-1)/4].r=a[(j-i-1)/4].r*10+s[i]-'0'; for(i=0,j=strlen(t);i<j;i++)a[(j-i-1)/4].i=a[(j-i-1)/4].i*10+t[i]-'0'; j=__builtin_ctz(N)-1; for(i=0;i<N;i++)pos[i]=pos[i>>1]>>1|((i&1)<<j); FFT(a,N,1); for(i=0;i<N;i++){ j=(N-i)&(N-1); b[i]=(a[i]*a[i]-(a[j]*a[j]).conj())*comp(0,-0.25); } FFT(b,N,-1); for(l=N-1,i=0;i<N;i++){ ans[i]+=(ll)(b[i].r+0.5); if(ans[i]>=B)ans[i+1]+=ans[i]/B; ans[i]%=B; } while(l&&!ans[l])l--; char*buf=::s; buf++; buf+=sprintf(buf,"%lld",ans[l]); for(l--;~l;l--)buf+=sprintf(buf,"%04lld",ans[l]); buf++; *buf=0; } } struct Num{ vector<ll>a;int len; Num(){ len=1; a.resize(len+1); a[1]=0; } Num(int k){ len=1; a.resize(len+1); a[1]=k; } void read(){ int i,j,l=strlen(s+1); while(!((s[l]>='0')&&(s[l]<='9')))l--; len=(l+K-1)/K; a.resize(len+1); for(i=1;i<=len;i++)a[i]=0; char ch; for(i=1,j=l;i<j;i++,j--)ch=s[i],s[i]=s[j],s[j]=ch; for(i=l;i;i--)(a[(i+K-1)/K]*=10)+=(s[i]-'0'); } void write(char*s){ s+=sprintf(s,"%lld",a[len]); for(int i=len-1;i;i--)s+=sprintf(s,"%016lld",a[i]); s++; *s=0; } }; int n,cnt,pool[13005]; Num solve(int l,int r){ if(l==r)return Num(pool[l]); int mid=(l+r)>>1; Num a=solve(l,mid),b=solve(mid+1,r); a.write(FFT::s); b.write(FFT::t); FFT::work(); Num c; c.read(); return c; } int main(){ scanf("%d",&n); if(n%4>1)return puts("0"),0; pool[cnt=1]=1; while(n>1)pool[++cnt]=n-2-n%2,n-=4; solve(1,cnt).write(s); puts(s); }
Task Rectangles 2
同Rectangles,答案要开long long。
Finals:
Type Two de Bruijn Sequences
从左往右贪心构造最坏的子序列,每次选择与当前字符不同的下一个字符,当下标超出$n$时构造交错序列。
#include<cstdio> int n,k,i,x;char a[1000005]; int main(){ scanf("%d%d%s",&n,&k,a+1); while(k--){ if(x<n){ for(i=x+2;i<=n;i++)if(a[i]!=a[x+1])break; x=i; }else x+=2; } if(x<n)x=n; printf("%d",x-n); }
Fibonacci Machine
Fib数可以看作$2\times 2$的转移矩阵的积乘以$2\times 1$的向量的某一项。
线段树维护区间向量和,打标记时乘以转移矩阵的幂,由于幂次不超过$O(m)$,因此预处理出转移矩阵的所有幂次后,打标记时不需要快速幂。
时间复杂度$O(m\log n)$。
#include<cstdio> const int N=100005,M=262155,P=1000000007; int n,m,i,c,d,tag[M],v[M][2],f[N][2][2],ans;char op[9]; inline void tag1(int x,int p){ tag[x]+=p; int A=v[x][0],B=v[x][1]; v[x][0]=(1LL*f[p][0][0]*A+1LL*f[p][0][1]*B)%P; v[x][1]=(1LL*f[p][1][0]*A+1LL*f[p][1][1]*B)%P; } inline void pb(int x){if(tag[x])tag1(x<<1,tag[x]),tag1(x<<1|1,tag[x]),tag[x]=0;} void build(int x,int a,int b){ v[x][1]=b-a+1; if(a==b)return; int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); } void change(int x,int a,int b){ if(c<=a&&b<=d){tag1(x,1);return;} pb(x); int mid=(a+b)>>1; if(c<=mid)change(x<<1,a,mid); if(d>mid)change(x<<1|1,mid+1,b); v[x][0]=(v[x<<1][0]+v[x<<1|1][0])%P; v[x][1]=(v[x<<1][1]+v[x<<1|1][1])%P; } void ask(int x,int a,int b){ if(c<=a&&b<=d){ans=(ans+v[x][0])%P;return;} pb(x); int mid=(a+b)>>1; if(c<=mid)ask(x<<1,a,mid); if(d>mid)ask(x<<1|1,mid+1,b); } int main(){ scanf("%d%d",&n,&m); f[0][0][0]=f[0][1][1]=1; for(i=1;i<=m;i++){ f[i][0][0]=f[i-1][1][0]; f[i][0][1]=f[i-1][1][1]; f[i][1][0]=(f[i-1][0][0]+f[i-1][1][0])%P; f[i][1][1]=(f[i-1][0][1]+f[i-1][1][1])%P; } build(1,1,n); while(m--){ scanf("%s%d%d",op,&c,&d); if(op[0]=='D')change(1,1,n); else{ ans=0; ask(1,1,n); printf("%d\n",ans); } } }
Colouring
图由若干个环构成,对于每个环二染色判断是否合法,答案为$2$的幂。
#include<cstdio> #include<cstdlib> const int N=105; int n,i,j,k,x,a[2][N],v[2][N],ap[N][2],m,f[N]; void dfs(int x,int y,int c){ if(v[x][y]){ if(v[x][y]!=c){ puts("0"); std::exit(0); } return; } v[x][y]=c; c=3-c; dfs(x^1,y,c); dfs(ap[a[x][y]][0]^x,ap[a[x][y]][1]^y,c); } int main(){ scanf("%d",&n); for(i=0;i<2;i++)for(j=0;j<n;j++){ scanf("%d",&a[i][j]); ap[a[i][j]][0]^=i; ap[a[i][j]][1]^=j; } f[m=1]=1; for(i=0;i<2;i++)for(j=0;j<n;j++)if(!v[i][j]){ dfs(i,j,1); for(k=1;k<=m;k++)f[k]<<=1; for(k=1;k<=m;k++)f[k+1]+=f[k]/10,f[k]%=10; if(f[m+1])m++; } for(i=m;i;i--)printf("%d",f[i]); }
Programming Contest
等价于保留不超过$n$个属性使得$1$号选手没有被别的选手偏序。
- 若$m\leq k$,则$m\leq 20$。设$dp[i][S]$表示考虑了前$i$个属性,淘汰掉了$S$集合的人时,最少需要保留多少个属性,时间复杂度$O(k2^m)$。
- 若$m>k$,则$k\leq 20$。$O(2^k)$暴力枚举所有方案,判断是否合法。
#include<cstdio> const int N=405,M=(1<<20)+5; int Case,n,m,k,cnt,flag,i,j,x,S,a[N],v[N][N],w[M];char f[M]; unsigned long long g[M][(N>>6)+1]; inline void up(char&a,char b){a>b?(a=b):0;} int main(){ for(i=1;i<M;i++)w[i]=w[i>>1]+(i&1); scanf("%d",&Case); while(Case--){ scanf("%d%d%d",&n,&m,&k); for(i=0;i<k;i++)scanf("%d",&a[i]); m--; for(i=0;i<m;i++)for(j=0;j<k;j++){ scanf("%d",&x); v[i][j]=x<a[j]; } if(m<=k){ for(i=0;i<1<<m;i++)f[i]=n+1; f[0]=0; for(i=0;i<k;i++){ for(S=j=0;j<m;j++)if(v[j][i])S|=1<<j; for(j=0;j<1<<m;j++)up(f[j|S],f[j]+1); } puts(f[(1<<m)-1]<=n?"TAK":"NIE"); }else{ flag=0; cnt=(m-1)>>6; for(i=0;i<k;i++){ for(j=0;j<=cnt;j++)g[1<<i][j]=0; for(j=0;j<m;j++)if(!v[j][i])g[1<<i][j>>6]|=1ULL<<(j&63); } for(S=1;S<1<<k;S++)if(w[S]<=n){ x=S&-S; if(x!=S)for(j=0;j<=cnt;j++)g[S][j]=g[x][j]&g[S^x][j]; for(j=0;j<=cnt;j++)if(g[S][j])break; if(j>cnt){flag=1;break;} } puts(flag?"TAK":"NIE"); } } }
Permutations
合法方案只有$O(n)$种,枚举$S$ ($1\leq S\leq n$且$S\geq n-S$),那么对应方案为"1..S n+1..n+n-S S+1..n n+n-S+1..n+n"。
如果存在上面匹配下面的情况,那么答案不超过$1$,可以暴力$O(n)$判断;否则也能$O(1)$判断一个$S$是否合法。
#include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; const int N=1000005; int n,m,cnt,i,x,y,flag,S,up,down,f[N<<1],g[N<<1],ans; void NIE(){ puts("0"); exit(0); } int main(){ scanf("%d%d",&n,&m); down=n+n+1; while(m--){ scanf("%d%d",&x,&y),f[x]=y; if(x==y){ if(x<=n)up=max(up,x); else down=min(down,x); }else{ flag=1; if(x>y)swap(x,y); S=x-y+n; } } if(flag){ if(S<1||S>n)NIE(); if(S<n-S)NIE(); for(i=1;i<=S;i++)g[++cnt]=i; for(i=n+1;i<=n+n-S;i++)g[++cnt]=i; for(i=S+1;i<=n;i++)g[++cnt]=i; for(i=n+n-S+1;i<=n+n;i++)g[++cnt]=i; for(i=1;i<=n+n;i++)if(f[i]&&f[i]!=g[i])NIE(); return puts("1"),0; } for(i=n;i>=n-i;i--)if(i>=up&&n+n-i+1<=down)ans++; printf("%d",ans); }
Watchmen
对于固定的点$P$来说,在$0$时刻将所有守卫分成两组:
- 背对$P$的守卫:在$[0,1]$里不断远离$P$,对$P$的限制不断放松。
- 面对$P$的守卫:在$[0,1]$里不断靠近$P$然后远离$P$,对$P$的限制不断放松。
因此时刻$1-\epsilon$一定是限制最松的时候,半平面交求出此时能走的凸区域$A$。
同理时刻$2-\epsilon$也是限制最松的时候,令此时能走的凸区域为$B$。
点$P$能走到$Q$只有以下几种情况:
- $Q$严格在$A$内部,$P$严格在$A$内部。
- $Q$严格在$B$内部,$P$严格在$B$内部。
- $P$和$Q$一个在$A$一个在$B$,且$A$和$B$严格有交集。
需要注意半平面交无穷的边界以及精度问题,时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long double ld; typedef long long ll; const int N=100010,inf=~0U>>1,M=20000010; const ld eps=1e-12; int n,m1,m2,m,i,mx,x,y,cl,isa[N<<1],isb[N<<1],pool[N<<1],flag,cnta,cntb,cntall; bool on[N]; struct E{ int x,y; E(){} E(int _x,int _y){x=_x,y=_y;} void read(){scanf("%d%d",&x,&y);} }a[N],b[N],d[N],c[N<<1]; struct P{ ld x,y; P(){x=y=0;} P(ld _x,ld _y){x=_x,y=_y;} P(const E&a){x=a.x,y=a.y;} P operator-(const P&a)const{return P(x-a.x,y-a.y);} P operator+(const P&a)const{return P(x+a.x,y+a.y);} P operator*(ld a)const{return P(x*a,y*a);} }p[N<<1]; struct L{ E p,v;int t; L(){} L(E _p,E _v,int _t){p=_p,v=_v,t=_t;} int loc()const{return v.y?v.y>0:v.x<0;} bool operator<(const L&b)const{ if(loc()!=b.loc())return loc()<b.loc(); return 1LL*v.x*b.v.y>1LL*v.y*b.v.x; } }line[N<<1],q[N<<1]; struct Line{ E s,d; ld cal(int x){return s.y+(((ld)x-s.x)*d.y)/d.x;} }hull[N]; inline ld cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;} inline ld cross(const P&a,const E&b){return a.x*b.y-a.y*b.x;} inline ld cross(const E&a,const P&b){return a.x*b.y-a.y*b.x;} inline ll cross(const E&a,const E&b){return 1LL*a.x*b.y-1LL*a.y*b.x;} inline void newL(const E&a,const E&b,int t){line[++cl]=L(a,b,t);} inline bool left(const P&p,const L&l){return cross(l.v,p-P(l.p))>eps;} inline P pos(const L&a,const L&b){ P x=P(a.p)-P(b.p);ld t=cross(b.v,x)/cross(a.v,b.v); return P(a.p)+P(a.v)*t; } inline bool halfplane(){ for(int i=0;i<n+4;i++)on[i]=0; sort(line+1,line+cl+1); int h=1,t=1; q[1]=line[1]; for(int i=2;i<=cl;i++){ while(h<t&&!left(p[t-1],line[i]))t--; while(h<t&&!left(p[h],line[i]))h++; if(!cross(q[t].v,line[i].v))q[t]=left(q[t].p,line[i])?q[t]:line[i]; else q[++t]=line[i]; if(h<t)p[t-1]=pos(q[t],q[t-1]); } while(h<t&&!left(p[t-1],q[h]))t--; p[t]=pos(q[t],q[h]); if(t<=h+1)return 0; for(int i=h;i<=t;i++)on[q[i].t]=1; return 1; } inline void init(int op){ for(int i=0;i<n;i++){ d[i].x=a[i].y-b[i].y; d[i].y=b[i].x-a[i].x; newL(b[i],d[i],i); } if(op){ b[n]=E(-M,-M); d[n]=E(1,0); b[n+1]=E(M,-M); d[n+1]=E(0,1); b[n+2]=E(M,M); d[n+2]=E(-1,0); b[n+3]=E(-M,M); d[n+3]=E(0,-1); for(int i=n;i<n+4;i++)newL(b[i],d[i],i); } } inline bool cmppool(int x,int y){return c[x].x<c[y].x;} inline bool cmphull(const Line&a,const Line&b){ return 1LL*a.d.y*b.d.x<1LL*b.d.y*a.d.x; } inline void solve(int*is){ if(!halfplane())return; int i,j,o,x,l=-inf,r=inf,cnt; for(i=0;i<n+4;i++)if(on[i]&&!d[i].x){ if(d[i].y<0)l=b[i].x; else r=b[i].x; } for(cnt=i=0;i<n+4;i++)if(on[i]&&d[i].x>0){ cnt++; hull[cnt].s=b[i]; hull[cnt].d=d[i]; } sort(hull+1,hull+cnt+1,cmphull); for(i=0,j=1;i<m;i++){ o=pool[i]; x=c[o].x; if(x<=l||x>=r)continue; while(j<cnt&&hull[j].cal(x)<hull[j+1].cal(x))j++; if(j<=cnt&&hull[j].cal(x)<c[o].y)is[o]++; } for(cnt=i=0;i<n+4;i++)if(on[i]&&d[i].x<0){ cnt++; hull[cnt].s=b[i]; hull[cnt].d.x=-d[i].x; hull[cnt].d.y=-d[i].y; } sort(hull+1,hull+cnt+1,cmphull); for(i=0,j=cnt;i<m;i++){ o=pool[i]; x=c[o].x; if(x<=l||x>=r)continue; while(j>1&&hull[j].cal(x)>hull[j-1].cal(x))j--; if(j>=1&&hull[j].cal(x)>c[o].y)is[o]++; } for(i=0;i<m;i++)is[i]=is[i]==2; } inline int ask(bool isa,bool isb){ if(!isa&&!isb)return 0; if((isa&&isb)||flag)return cntall; return isa?cnta:cntb; } int main(){ scanf("%d%d%d",&n,&m1,&m2); m=m1+m2; for(i=0;i<n;i++)a[i].read(),b[i].read(); for(i=0;i<m;i++)c[i].read(),pool[i]=i; for(i=0;i<n;i++){ mx=max(mx,a[i].x>0?a[i].x:-a[i].x); mx=max(mx,a[i].y>0?a[i].y:-a[i].y); mx=max(mx,b[i].x>0?b[i].x:-b[i].x); mx=max(mx,b[i].y>0?b[i].y:-b[i].y); } sort(pool,pool+m,cmppool); cl=0; init(1); solve(isa); cl=0; for(i=0;i<n;i++)swap(a[i],b[i]); init(1); solve(isb); cl=0; init(mx<=1000); for(i=0;i<n;i++)swap(a[i],b[i]); init(0); flag=halfplane(); for(i=m1;i<m;i++){ if(isa[i])cnta++; if(isb[i])cntb++; if(isa[i]||isb[i])cntall++; } for(i=0;i<m1;i++)printf("%d\n",ask(isa[i],isb[i])); }
Tram
即求经过每个点的所有环的环长的gcd。
对于每个SCC分开做,同一SCC内每个点的答案都相同。
对于原图的某条边$(u,v,w)$,拆成两条边:
- $u\rightarrow v$,边权$w$。
- $v\rightarrow u$,边权$-w$。
任取一点找出DFS树,对于每条非树边,将对应的环长加入gcd。
时间复杂度$O(n+m\log w)$。
#include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef pair<int,int>P; typedef vector<P>V; #define rep(it,v) for(V::iterator it=v.begin();it!=v.end();it++) const int N=100005; int n,m,cnt,i,x,y,z,cur,t,q[N],at[N],f[N],ans[N]; bool vis[N]; V g[N],h[N]; void dfs1(int x){ if(vis[x])return; vis[x]=1; rep(it,g[x])dfs1(it->first); q[++t]=x; } void dfs2(int x){ if(!vis[x])return; vis[x]=0,at[x]=cnt; rep(it,h[x])dfs2(it->first); } int gcd(int a,int b){ if(a<0)a=-a; if(b<0)b=-b; if(!a||!b)return a+b; return gcd(b,a%b); } void dfs3(int x,int y){ if(vis[x]){ cur=gcd(cur,y-f[x]); return; } vis[x]=1; f[x]=y; rep(it,g[x])if(at[x]==at[it->first])dfs3(it->first,y+it->second); rep(it,h[x])if(at[x]==at[it->first])dfs3(it->first,y-it->second); } int main(){ scanf("%d%d",&n,&m); while(m--){ scanf("%d%d%d",&x,&y,&z); g[x].push_back(P(y,z)); h[y].push_back(P(x,z)); } for(i=1;i<=n;i++)if(!vis[i])dfs1(i); for(i=n;i;i--)if(vis[q[i]])cnt++,dfs2(q[i]); for(i=1;i<=n;i++)if(!ans[at[i]]){ cur=0; dfs3(i,0); ans[at[i]]=cur?cur:-1; } for(i=1;i<=n;i++)printf("%d\n",ans[at[i]]); }