Potyczki Algorytmiczne 2011
Trial Round:
Tulips
按题意模拟。
#include<cstdio> const int N=15000; int n,ans=N,x,v[N+1]; int main(){ scanf("%d",&n); while(n--){ scanf("%d",&x); if(!v[x])ans--,v[x]=1; } printf("%d",ans); }
Round 1:
Rooks [B]
对于每个没有车的行,随便找一个没有车的列配对。
#include<cstdio> const int N=1005; int n,i,j,a[N],b[N];char tmp[N]; int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%s",tmp+1); for(j=1;j<=n;j++)if(tmp[j]=='W')b[a[i]=j]=1; } for(i=1;i<=n;i++)if(!a[i])for(j=1;j<=n;j++)if(!b[j]){b[a[i]=j]=1;break;} for(i=1;i<=n;i++){ for(j=1;j<=n;j++)tmp[j]='.'; tmp[a[i]]='W'; puts(tmp+1); } }
Round 2:
Unlucky [A]
如果$w$不是$k$的倍数,那么先考虑从起点带$w\bmod k$升水出发怎么处理,然后再依次考虑每次从起点带$k$升水出发怎么处理。
假设目前已经推进到的右端点是$all$,算上这次一共要从起点出发$t$次,那么从$all$往右推进的这段路将被经过总计$2t-1$次。最优策略是将这次出发带走的水平均分成$2t-1$份,从而得到本次推进的距离,注意距离要和到终点的距离取$\min$。
求出推进距离后,乘以经过的次数,就是这段路消耗的水量。
时间复杂度$O(\frac{w}{k})$。
#include<cstdio> typedef double db; int s,w,k,o;db sum,all; inline void gao(db k){ k/=o; if(k+all>=s)k=s-all; sum+=k*o; all+=k; o-=2; } int main(){ scanf("%d%d%d",&s,&w,&k); o=(w/k)*2-1; if(w%k)o+=2,gao(w%k); while(o>0)gao(k); printf("%.3f",w-sum); }
Climbing [B]
从左往右贪心,若一段区间能拼上则拼上,通过维护关于最左边数的不等式组的解集来判断。
时间复杂度$O(n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; int n,i,ans,A,B;ll L,R,C,D; inline bool check(ll K){ if(C==1){ L=max(L,-D); R=min(R,K-D); }else{ L=max(L,D-K); R=min(R,D); } return L<=R; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d%d",&A,&B); A+=B; if(i>1&&check(A)){ ans++; D=A-D; C=-C; }else{ L=0,R=A; C=-1,D=A; } } printf("%d",ans); }
Round 3:
Pedestrian Crossing [B]
在模$K$意义下做,每个白色段会禁掉起点位置的一个区间,注意这里区间端点不会被禁,所以引入$0.5$坐标。最后检查是否$[0,K-0.5]$都被禁了即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; typedef pair<ll,ll>P; const int N=500005; int Case,n,m,i; ll S,K,l,r,sum,len[N]; P e[N<<1]; inline void ban(ll l,ll r){ if(l>r)return; e[++m]=P(l,r); } inline bool gao(ll l,ll r){ if(l+K-S<r)return 0; l%=K,r%=K; if(l>r)r+=K; l+=K-S,r+=K; if(l/K==r/K)ban(l%K*2+1,r%K*2-1); else{ ban(l%K*2+1,K*2-1); ban(0,r%K*2-1); } return 1; } bool check(){ scanf("%lld%lld%d",&S,&K,&n); for(i=1;i<=n;i++)scanf("%lld",&len[i]),len[i]+=len[i-1]; m=0; for(i=1;i<=n;i+=2)if(!gao(len[i-1],len[i]))return 0; sort(e+1,e+m+1); l=0,r=-5,sum=0; for(i=1;i<=m;i++){ if(e[i].first>r+1){ if(l<=r)sum+=r-l+1; l=e[i].first; } r=max(r,e[i].second); } if(l<=r)sum+=r-l+1; return sum<K*2; } int main(){ scanf("%d",&Case); while(Case--)puts(check()?"TAK":"NIE"); }
Round 4:
Fuel [B]
找到一条树直径,令直径上的点代价为$1$,剩下的点代价为$2$,注意起点代价为$0$,然后贪心选点即可。
时间复杂度$O(n)$。
#include<cstdio> const int N=500005; int n,m,i,x,y,g[N],v[N<<1],nxt[N<<1],ed,A,B,ans; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y,int d){ if(d>B)A=x,B=d; for(int i=g[x];i;i=nxt[i])if(v[i]!=y)dfs(v[i],x,d+1); } int main(){ scanf("%d%d",&n,&m); for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); dfs(1,0,1); dfs(A,0,1); A=n-B; ans=1,B--; while(m&&B)m--,B--,ans++; while(m>1&&A)m-=2,A--,ans++; printf("%d",ans); }
Round 5:
Declining Sequences [B]
求出$f[i][j]$表示有多少以$j$为起点长度为$i$的递减序列。
对于一个有解的询问,从第$1$层一直找到第$m$层,每层需要找到权值小于某数,且下标大于某数的部分里从左往右第$k$个方案所在的下标。
按照权值扫描线后,线段树上维护区间$f$值的和,然后在线段树上二分即可。
时间复杂度$O(nm\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=100005,K=13,M=262155; const ll inf=1000000000000000010LL; int n,m,q,i,j,k,o,a[N],b[N],ans[N][K];ll f[K][N],bit[N],que[N],all; int gn[N],gq[N],v[N<<1],nxt[N<<1],ed; int pos[N],A,B;ll val[M],C; inline void up(ll&a,ll b){a=a+b<inf?a+b:inf;} inline void ins(int x,ll p){for(;x<=n;x+=x&-x)up(bit[x],p);} inline ll ask(int x){ll t=0;for(;x;x-=x&-x)up(t,bit[x]);return t;} inline void add(int&x,int y){v[++ed]=y;nxt[ed]=x;x=ed;} void build(int x,int a,int b){ val[x]=0; if(a==b){pos[a]=x;return;} int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); } inline void modify(int x,ll p){ val[x=pos[x]]=p; for(x>>=1;x;x>>=1)val[x]=val[x<<1],up(val[x],val[x<<1|1]); } void query(int x,int a,int b){ if(B)return; if(A<=a&&val[x]<C){C-=val[x];return;} if(a==b){B=a;return;} int mid=(a+b)>>1; if(A<=mid)query(x<<1,a,mid); query(x<<1|1,mid+1,b); } int main(){ scanf("%d%d%d",&n,&m,&q); for(i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i]; sort(b+1,b+n+1); for(i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n+1,a[i])-b; for(i=1;i<=n;i++)f[m][i]=1; for(i=m-1;i;i--){ for(j=1;j<=n;j++)bit[j]=0; for(j=n;j;j--){ f[i][j]=ask(a[j]-1); ins(a[j],f[i+1][j]); } } for(i=1;i<=n;i++)up(all,f[1][i]); a[0]=n+1; for(i=1;i<=q;i++){ scanf("%lld",&que[i]); if(que[i]>all)ans[i][0]=-1; } for(i=1;i<=n;i++)add(gn[a[i]],i); for(i=1;i<=m;i++){ ed=n; for(j=0;j<=n;j++)gq[j]=0; for(j=1;j<=q;j++)if(~ans[j][0])add(gq[a[ans[j][i-1]]-1],j); build(1,1,n); for(j=0;j<=n;j++){ for(k=gn[j];k;k=nxt[k]){ o=v[k]; modify(o,f[i][o]); } for(k=gq[j];k;k=nxt[k]){ o=v[k]; A=ans[o][i-1]+1; B=0; C=que[o]; query(1,1,n); ans[o][i]=B; que[o]=C; } } } for(i=1;i<=q;i++){ if(~ans[i][0])for(j=1;j<=m;j++)printf("%d%c",ans[i][j],j<m?' ':'\n'); else puts("-1"); } }
Double Factorial [B]
$1!\times 2!\times3!\times\dots\times n!$里质因子$5$的个数即
\[\left(\lfloor\frac{1}{5^1}\rfloor+\lfloor\frac{2}{5^1}\rfloor+\dots+\lfloor\frac{n}{5^1}\rfloor\right)+\left(\lfloor\frac{1}{5^2}\rfloor+\lfloor\frac{2}{5^2}\rfloor+\dots+\lfloor\frac{n}{5^2}\rfloor\right)+\dots+\left(\lfloor\frac{1}{5^k}\rfloor+\lfloor\frac{2}{5^k}\rfloor+\dots+\lfloor\frac{n}{5^k}\rfloor\right)\]
枚举$k$后推公式即可。
n=int(input()) ans=0 k=5 while k<=n: m=n//k ans+=(m-1)*m*k//2+m*(n-m*k+1) k*=5 print(ans)
Trails [A]
留坑。
Vacation [A]
当$k=2$时就是$a$和$b$的距离。
当$k=3$时,答案上界为$24n$,对于每个点向三个排列对应位置左右$15$个位置连负边可以减小答案。建立二分图,使用Dijkstra找增广路求最小费用流。
优化1:
最短路不超过$24$,可以用桶代替堆,使得每次Dijkstra时间复杂度为$O(n+m)$,其中$m=nc$。
优化2:
Dijkstra求出源点到每个点的最短路后,在最短路图上BFS得到每个点的层数。
沿着最短层进行多路增广,不断BFS多路增广直到无法增广。
那么每次Dijkstra求出的最小花费严格递增,且BFS出来的最小层数严格递增。
沿用Dinic的分析可得每次多路增广次数不超过$O(\sqrt{n})$,所以一共$O(c\sqrt{n})$次多路增广。
总时间复杂度$O(c\sqrt{n}(n+m))$=$O(c^2n^{1.5})$。
#include<cstdio> const int N=5005,inf=10000000,K=30; int n,k,i,j,ans,a[N],b[N],c[N],last[N]; int s,t,cnt=1; int g[N<<1],d[N<<1],f[N<<1],vis[N<<1],h[N<<1]; int pool[K][N<<1],top[K],q[N<<1]; int cur[N<<1]; void read(int a[]){ int i,x; for(i=1;i<=n;i++)scanf("%d",&x),a[x]=i; } inline int abs(int x){return x>0?x:-x;} inline int min(int a,int b){return a<b?a:b;} struct E{ int v,f,c,nxt; E(){} E(int _v,int _f,int _c,int _nxt){v=_v,f=_f,c=_c,nxt=_nxt;} }e[N*(15*3+2)*2]; inline void add(int u,int v,int f,int c){ e[++cnt]=E(v,f,c,g[u]); g[u]=cnt; e[++cnt]=E(u,0,-c,g[v]); g[v]=cnt; } inline void addedge(int x,int y){ if(y<1||y>n)return; if(last[y]==x)return; last[y]=x; int w=min(abs(y-a[x]),8)+min(abs(y-b[x]),8)+min(abs(y-c[x]),8); add(x,y+n,1,w-24); } inline void ext(int x,int y){ if(y>=K)return; d[x]=y; pool[y][++top[y]]=x; } inline bool dijkstra(){ int i,o; for(i=1;i<=t;i++)d[i]=inf,vis[i]=0; for(i=0;i<K;i++)top[i]=0; ext(s,0); for(o=0;o<K;o++)while(top[o]){ int u=pool[o][top[o]--]; if(vis[u])continue; vis[u]=1; for(i=g[u];i;i=e[i].nxt)if(e[i].f){ int v=e[i].v,w=e[i].c+h[u]-h[v]; if(d[v]>d[u]+w&&!vis[v])ext(v,d[u]+w); } } return d[t]!=inf; } inline bool bfs(){ int head=1,tail=1,i,u,v,z; for(i=1;i<=t;i++)vis[i]=0; vis[s]=1; f[s]=0; q[1]=s; while(head<=tail){ u=q[head++]; z=f[u]+1; for(i=g[u];i;i=e[i].nxt)if(e[i].f){ int v=e[i].v,w=e[i].c+h[u]-h[v]; if(d[v]==d[u]+w&&!vis[v]){ vis[v]=1; f[v]=z; q[++tail]=v; } } } return vis[t]; } bool dfs(int u){ if(u==t)return 1; vis[u]=1; for(int&i=cur[u];i;i=e[i].nxt){ int v=e[i].v; if(!vis[v]&&e[i].f&&d[v]==d[u]+e[i].c+h[u]-h[v]&&f[v]==f[u]+1){ int tmp=dfs(v); if(tmp){ ans+=e[i].c; e[i].f--,e[i^1].f++; vis[u]=0; i=e[i].nxt; return 1; } } } return vis[u]=0; } int main(){ scanf("%d%d",&n,&k); read(a); read(b); if(k==2){ for(i=1;i<=n;i++)ans+=min(abs(a[i]-b[i]),8); return printf("%d",ans),0; } read(c); s=n*2+1; t=s+1; for(i=1;i<=n;i++)add(s,i,1,0),add(i+n,t,1,0); for(i=1;i<=n;i++){ for(j=a[i]-7;j<=a[i]+7;j++)addedge(i,j); for(j=b[i]-7;j<=b[i]+7;j++)addedge(i,j); for(j=c[i]-7;j<=c[i]+7;j++)addedge(i,j); } for(i=1;i<=n;i++)for(j=g[i];j;j=e[j].nxt)h[e[j].v]=min(h[e[j].v],e[j].c); for(i=1;i<=n;i++)h[t]=min(h[t],h[i+n]); ans=n*24; while(dijkstra()){ if(h[t]+d[t]>=0)break; while(bfs()){ for(i=1;i<=t;i++)cur[i]=g[i],vis[i]=0; while(dfs(s)); } for(i=1;i<=t;i++)h[i]+=d[i]; } printf("%d",ans); }
Round 6:
Automorphisms [B]
找重心作为根,如果两个重心就拆了中间加个点作为根。
对于每个点,将儿子按hash值分组,对于每组,将答案乘以儿子数的阶乘即可。
#include<cstdio> #include<vector> #include<algorithm> #include<map> using namespace std; typedef vector<int>V; const int N=500005,P=1000000007; int n,m,cnt,i,x,y,A,B,ans,g[N],v[N<<1],nxt[N<<1],ed,fac[N],sz[N],f[N],q[N]; V tmp;map<V,int>T; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ sz[x]=1; for(int i=g[x];i;i=nxt[i])if(v[i]!=y)dfs(v[i],x),sz[x]+=sz[v[i]]; if(sz[x]>n-sz[x]&&!A)A=x; if(sz[x]==n-sz[x])A=x,B=y; } void cal(int x,int y){ for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; cal(u,x); } cnt=0; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; q[cnt++]=f[u]; } sort(q,q+cnt); for(int i=0,j;i<cnt;i=j){ for(j=i;j<cnt&&q[i]==q[j];j++); ans=1LL*ans*fac[j-i]%P; } tmp.resize(cnt); for(int i=0;i<cnt;i++)tmp[i]=q[i]; int&o=T[tmp]; if(!o)o=++m; f[x]=o; } int main(){ scanf("%d",&n); for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); for(fac[0]=i=1;i<=n;i++)fac[i]=1LL*fac[i-1]*i%P; dfs(1,0); ans=1; cal(A,B); if(B){ cal(B,A); if(f[A]==f[B])ans=ans*2%P; } printf("%d",ans); }
Kangaroos [A]
两个区间$[x_1,y_1][x_2,y_2]$相交等价于$x_1\leq y_2$且$y_1\geq x_2$。
考虑离线,把所有区间看成二维的点建立K-D Tree。
然后从$1$到$n$依次加入每个区间,每次加入一个区间时,把所有与它相交的询问的值$+1$,把所有与它不相交的询问的值设为$0$。
在K-D Tree上打标记,最后每个询问的答案为其值的历史最大值,时间复杂度$O(n\sqrt{m})$。
#include<cstdio> #include<algorithm> const int N=200010,inf=-1,BUF=6000000; int n,m,i,id[N],root,cmp_d,X,Y;struct P{int x,y;}a[N];char Buf[BUF],*buf=Buf; inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} struct node{ int D[2],l,r,Max[2],Min[2]; int m,d,e,hm,hd,he; }t[N]; inline bool cmp(const node&a,const node&b){return a.D[cmp_d]<b.D[cmp_d];} inline void Max(int&a,int b){if(a<b)a=b;} inline void Min(int&a,int b){if(a>b)a=b;} inline void up(int x){ id[t[x].e]=x,t[x].e=t[x].he=inf; if(t[x].l){ Max(t[x].Max[0],t[t[x].l].Max[0]); Min(t[x].Min[0],t[t[x].l].Min[0]); Max(t[x].Max[1],t[t[x].l].Max[1]); Min(t[x].Min[1],t[t[x].l].Min[1]); } if(t[x].r){ Max(t[x].Max[0],t[t[x].r].Max[0]); Min(t[x].Min[0],t[t[x].r].Min[0]); Max(t[x].Max[1],t[t[x].r].Max[1]); Min(t[x].Min[1],t[t[x].r].Min[1]); } } int build(int l,int r,int D){ int mid=(l+r)>>1; cmp_d=D,std::nth_element(t+l+1,t+mid+1,t+r+1,cmp); t[mid].Max[0]=t[mid].Min[0]=t[mid].D[0]; t[mid].Max[1]=t[mid].Min[1]=t[mid].D[1]; if(l!=mid)t[mid].l=build(l,mid-1,!D); if(r!=mid)t[mid].r=build(mid+1,r,!D); return up(mid),mid; } inline void hdoa(node&x,int v){ Max(x.hm,x.m+v); if(x.e>inf)Max(x.he,x.e+v);else Max(x.hd,x.d+v); } inline void hdoc(node&x,int v){Max(x.hm,v);Max(x.he,v);} inline void doa(node&x,int v){ Max(x.hm,x.m+=v); if(x.e>inf)Max(x.he,x.e+=v);else Max(x.hd,x.d+=v); } inline void doc(node&x,int v){Max(x.hm,x.m=v);Max(x.he,x.e=v);x.d=0;} inline void pb(node&x){ if(x.hd){ if(x.l)hdoa(t[x.l],x.hd); if(x.r)hdoa(t[x.r],x.hd);x.hd=0; } if(x.he>inf){ if(x.l)hdoc(t[x.l],x.he); if(x.r)hdoc(t[x.r],x.he); x.he=inf; } if(x.d){ if(x.l)doa(t[x.l],x.d); if(x.r)doa(t[x.r],x.d); x.d=0; }else if(x.e>inf){ if(x.l)doc(t[x.l],x.e); if(x.r)doc(t[x.r],x.e); x.e=inf; } } void change(node&x){ if(x.Min[0]>X||x.Max[1]<Y){doc(x,0);return;} if(x.Max[0]<=X&&x.Min[1]>=Y){doa(x,1);return;} pb(x); if(x.D[0]<=X&&x.D[1]>=Y)Max(x.hm,++x.m);else x.m=0; if(x.l)change(t[x.l]); if(x.r)change(t[x.r]); } void dfs(node&x){ pb(x); if(x.l)dfs(t[x.l]); if(x.r)dfs(t[x.r]); } int main(){ fread(Buf,1,BUF,stdin),read(n),read(m); for(i=1;i<=n;i++)read(a[i].x),read(a[i].y); for(i=1;i<=m;i++)read(t[i].D[0]),read(t[i].D[1]),t[i].e=i; root=build(1,m,0); for(i=1;i<=n;i++)X=a[i].y,Y=a[i].x,change(t[root]); dfs(t[root]); for(i=1;i<=m;i++)printf("%d\n",t[id[i]].hm); }
Laser Pool [A]
与横线以及竖线的交点个数很容易求,那么只要求出横线竖线交点与运动轨迹的交点数即可。
运动轨迹可以划分成若干条贯穿边界的斜线,对于第一条和最后一条,可以用bitset暴力统计。
对于中间的部分,斜线都是完整的,可以FFT预处理。
时间复杂度$O(n\log n+\frac{nq}{32})$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; typedef unsigned int U; const int N=100010,M=10010,L=262150; int n,m,cb,ce,i,j,ab[N*2],arb[N*2],rab[N*2],rarb[N*2],g[65540]; char a[N],b[N]; struct Que{int x,y,vx,vy,t,ans;}e[M]; inline int popcount(U x){return g[x>>16]+g[x&65535];} struct BIT{ U v[N/32+5]; void clr(){for(int i=0;i<=cb;i++)v[i]=0;} U get(int x){return v[x>>5]>>(x&31)&1;} void set(int x,U y){if((v[x>>5]>>(x&31)&1)^y)v[x>>5]^=1U<<(x&31);} void shl(int x,int y){ int A=y>>5,B=y&31,C=(32-B)&31,D=x>>5,E=(D<<5)+31; for(int i=x;i<=E;i++)set(i,get(i+y)); for(int i=D+1;i<=cb;i++){ v[i]=v[i+A]>>B; if(C)v[i]|=v[i+A+1]<<C; } } void copy(int x,int y,const BIT&p){for(int i=x;i<=y;i++)v[i]=p.v[i];} void And(int x,int y,const BIT&p){for(int i=x;i<=y;i++)v[i]&=p.v[i];} int count(int x,int y){ int A=x>>5,B=y>>5,C,ret=0; if(A==B){ for(int i=x;i<=y;i++)if(v[A]>>(i&31)&1)ret++; return ret; } for(int i=A+1;i<B;i++)ret+=popcount(v[i]); C=(A<<5)+31; for(int i=x;i<=C;i++)if(v[A]>>(i&31)&1)ret++; C=B<<5; for(int i=C;i<=y;i++)if(v[B]>>(i&31)&1)ret++; return ret; } }bA,bB,brA,brB,tA,tB; inline void read(int&a){ char c;bool f=0;a=0; while(!((((c=getchar())>='0')&&(c<='9'))||(c=='-'))); if(c!='-')a=c-'0';else f=1; while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0'; if(f)a=-a; } namespace FFT{ int k,j,pos[L]; const double pi=acos(-1.0); struct comp{ double r,i;comp(double _r=0,double _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);} }a[L],ra[L],b[L],rb[L],c[L]; 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; double 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(){ for(k=1;k<=n||k<=m;k<<=1);k<<=1; j=__builtin_ctz(k)-1; for(i=0;i<k;i++)pos[i]=pos[i>>1]>>1|((i&1)<<j); for(i=1;i<=n;i++)a[i].r=ra[n-i+1].r=::a[i]; for(i=1;i<=m;i++)b[i].r=rb[m-i+1].r=::b[i]; FFT(a,k,1),FFT(ra,k,1),FFT(b,k,1),FFT(rb,k,1); for(i=0;i<k;i++)c[i]=a[i]*b[i]; FFT(c,k,-1); for(i=1;i<=n+m;i++)ab[i]=(int)(c[i].r+0.5); for(i=0;i<k;i++)c[i]=a[i]*rb[i]; FFT(c,k,-1); for(i=1;i<=n+m;i++)arb[i]=(int)(c[i].r+0.5); for(i=0;i<k;i++)c[i]=ra[i]*b[i]; FFT(c,k,-1); for(i=1;i<=n+m;i++)rab[i]=(int)(c[i].r+0.5); for(i=0;i<k;i++)c[i]=ra[i]*rb[i]; FFT(c,k,-1); for(i=1;i<=n+m;i++)rarb[i]=(int)(c[i].r+0.5); } } namespace Solve{ bool a[N],b[N]; int sa[N],sb[N]; int cnt,idx[4][N],idy[4][N]; int tot,st[N*8],en[N*8],v[N*8],pos[N*8],cur,q[N*8],sw[N*8];long long sl[N*8]; struct E{int sx,sy,ex,ey,len,w,d,nxt;}f[N*8]; inline bool check(int x,int y){return b[x]&&a[y];} inline int abs(int x){return x>0?x:-x;} inline int&getid(int x,int y,int d){ if(y==1||y==n)return idx[d][x]; return idy[d][y]; } inline void makerev(int x){ f[x].sx=f[x-1].ex; f[x].sy=f[x-1].ey; f[x].ex=f[x-1].sx; f[x].ey=f[x-1].sy; f[x].w=f[x-1].w; f[x].d=(f[x-1].d+2)&3; } inline int getnxt(int x,int y,int d){ if(d==0){ if(x<m&&y==n)return getid(x,y,(d+1)&3); if(y==n)return getid(x,y,(d+2)&3); return getid(x,y,(d+3)&3); } if(d==2){ if(x>1&&y==1)return getid(x,y,(d+1)&3); if(y==1)return getid(x,y,(d+2)&3); return getid(x,y,(d+3)&3); } if(d==1){ if(x<m&&y==1)return getid(x,y,(d+3)&3); if(y==1)return getid(x,y,(d+2)&3); return getid(x,y,(d+1)&3); } if(x>1&&y==n)return getid(x,y,(d+3)&3); if(y==n)return getid(x,y,(d+2)&3); return getid(x,y,(d+1)&3); } inline int cal(int x,int t,int n,int*s){ if(x+t<=n)return s[x+t]-s[x-1]; t-=n-x; int ret=s[n-1]-s[x-1]; ret+=t/(n+n-2)*(s[n]+s[n-1]-s[1]); t%=n+n-2; if(t<n)return ret+s[n]-s[n-t-1]; return ret+s[n]-s[1]+s[t-n+2]; } inline int search(int L,int r,int x){ int l=L,t=L-1,mid; while(l<=r)if(sl[mid=(l+r)>>1]-sl[L-1]<=x)l=(t=mid)+1;else r=mid-1; return t; } inline void ask(int x,int y,int t,int p){ int&ans=e[p].ans; ans=cal(x,t,m,sb)+cal(y,t,n,sa); int ex=m,ey=y-x+m; if(ey>n)ey=n,ex=x-y+n; int d=ex-x; if(t<=d){ tA.copy(y>>5,cb,bA); tB.copy(x>>5,cb,bB); tB.shl(0,x); tA.shl(0,y); tA.And(0,t>>5,tB); ans-=tA.count(0,t); return; } if(d){ tA.copy(y>>5,cb,bA); tB.copy(x>>5,cb,bB); tB.shl(0,x); tA.shl(0,y); tA.And(0,(d-1)>>5,tB); ans-=tA.count(0,d-1); } t-=d; int o=getnxt(ex,ey,0); int l=st[v[o]],r=en[v[o]]; o=pos[o]; int u=search(o,r,t); ans-=sw[u]-sw[o-1]; t-=sl[u]-sl[o-1]; o=u+1; if(o>r){ ans-=t/(sl[r]-sl[l-1])*(sw[r]-sw[l-1]); t%=sl[r]-sl[l-1]; u=search(l,r,t); ans-=sw[u]-sw[l-1]; t-=sl[u]-sl[l-1]; o=u+1; } o=q[o]; x=f[o].sx,y=f[o].sy; if(f[o].d==0){ tA.copy(y>>5,cb,bA); tB.copy(x>>5,cb,bB); } if(f[o].d==1){ y=n-y+1; tA.copy(y>>5,cb,brA); tB.copy(x>>5,cb,bB); } if(f[o].d==2){ x=m-x+1; y=n-y+1; tA.copy(y>>5,cb,brA); tB.copy(x>>5,cb,brB); } if(f[o].d==3){ x=m-x+1; tA.copy(y>>5,cb,bA); tB.copy(x>>5,cb,brB); } tB.shl(0,x); tA.shl(0,y); tA.And(0,t>>5,tB); ans-=tA.count(0,t); } void work(int*A,int*B){ for(i=1;i<=n;i++)sa[i]=sa[i-1]+a[i],bA.set(i,a[i]),brA.set(n-i+1,a[i]); for(i=1;i<=m;i++)sb[i]=sb[i-1]+b[i],bB.set(i,b[i]),brB.set(m-i+1,b[i]); cnt=0; for(i=1;i<m;i++){ cnt++; f[cnt].sx=i,f[cnt].sy=1; f[cnt].ex=m,f[cnt].ey=m-i+1; if(f[cnt].ey>n)f[cnt].ey=n,f[cnt].ex=n-1+i; f[cnt].w=B[m-i+2]; f[cnt].d=0; makerev(++cnt); } for(i=2;i<n;i++){ cnt++; f[cnt].sx=1,f[cnt].sy=i; f[cnt].ex=m,f[cnt].ey=m-1+i; if(f[cnt].ey>n)f[cnt].ey=n,f[cnt].ex=n-i+1; f[cnt].w=B[m+i]; f[cnt].d=0; makerev(++cnt); } for(i=2;i<=m;i++){ cnt++; f[cnt].sx=i,f[cnt].sy=1; f[cnt].ex=1,f[cnt].ey=i; if(f[cnt].ey>n)f[cnt].ey=n,f[cnt].ex=i+1-n; f[cnt].w=A[i+1]; f[cnt].d=3; makerev(++cnt); } for(i=2;i<n;i++){ cnt++; f[cnt].sx=m,f[cnt].sy=i; f[cnt].ex=1,f[cnt].ey=m+i-1; if(f[cnt].ey>n)f[cnt].ey=n,f[cnt].ex=m+i-n; f[cnt].w=A[i+m]; f[cnt].d=3; makerev(++cnt); } for(i=1;i<=cnt;i++){ f[i].len=abs(f[i].sx-f[i].ex); f[i].w-=check(f[i].ex,f[i].ey); getid(f[i].sx,f[i].sy,f[i].d)=i; } for(i=1;i<=cnt;i++)f[i].nxt=getnxt(f[i].ex,f[i].ey,f[i].d); cur=tot=0; for(i=1;i<=cnt;i++)v[i]=0; for(i=1;i<=cnt;i++)if(!v[i]){ st[++tot]=cur+1; for(j=i;!v[j];j=f[j].nxt)v[q[++cur]=j]=tot; en[tot]=cur; } for(i=1;i<=cnt;i++)sl[i]=sl[i-1]+f[q[i]].len,sw[i]=sw[i-1]+f[q[i]].w,pos[q[i]]=i; } } int main(){ for(i=1;i<65536;i++)g[i]=g[i>>1]+(i&1); read(n),read(m); scanf("%s%s",a+1,b+1); for(i=1;i<=n;i++)a[i]-='0'; for(i=1;i<=m;i++)b[i]-='0'; read(ce); for(i=1;i<=ce;i++)read(e[i].x),read(e[i].y),read(e[i].vx),read(e[i].vy),read(e[i].t); FFT::work(); cb=(n>m?n:m)>>5; for(i=1;i<=n;i++)Solve::a[i]=a[i]; for(i=1;i<=m;i++)Solve::b[i]=b[i]; Solve::work(ab,arb); for(i=1;i<=ce;i++)if(e[i].vx==1&&e[i].vy==1)Solve::ask(e[i].x,e[i].y,e[i].t,i); for(i=1;i<=n;i++)Solve::a[i]=a[n-i+1]; for(i=1;i<=m;i++)Solve::b[i]=b[i]; Solve::work(rab,rarb); for(i=1;i<=ce;i++)if(e[i].vx==1&&e[i].vy==-1)Solve::ask(e[i].x,n-e[i].y+1,e[i].t,i); for(i=1;i<=n;i++)Solve::a[i]=a[i]; for(i=1;i<=m;i++)Solve::b[i]=b[m-i+1]; Solve::work(arb,ab); for(i=1;i<=ce;i++)if(e[i].vx==-1&&e[i].vy==1)Solve::ask(m-e[i].x+1,e[i].y,e[i].t,i); for(i=1;i<=n;i++)Solve::a[i]=a[n-i+1]; for(i=1;i<=m;i++)Solve::b[i]=b[m-i+1]; Solve::work(rarb,rab); for(i=1;i<=ce;i++)if(e[i].vx==-1&&e[i].vy==-1)Solve::ask(m-e[i].x+1,n-e[i].y+1,e[i].t,i); for(i=1;i<=ce;i++)printf("%d\n",e[i].ans); }
The Shortest Period [B]
枚举答案长度$L$,设$A$和$B$分别为第一个循环节和反串的第一个循环节:
1. 坏点不在$A$,那么可以暴力匹配检验。
2. 坏点不在$B$,那么把串翻转后不在$A$中,转化为$1$。
3. 坏点在$A$和$B$的交里面,那么只要长度为$N-L+1$的前后缀相同,那么就存在长度为$L$的循环节。
通过扩展KMP和Hash快速判断即可,时间复杂度$O(dn\log n)$。
#include<cstdio> const int N=200010,P=233; int T,n,i,j,t,k,p,l,ans,g[N];char a[N];unsigned int pow[N],f[N]; inline void swap(char&a,char&b){char c=a;a=b;b=c;} inline unsigned int hash(int l,int r){return f[r]-f[l-1]*pow[r-l+1];} inline int min(int a,int b){return a<b?a:b;} void solve(){ for(i=1;i<=n;i++)f[i]=f[i-1]*P+a[i]; for(g[i=0]=n;i<n-1&&a[i+1]==a[i+2];i++); for(g[t=1]=i,k=2;k<n;k++){ p=t+g[t]-1,l=g[k-t]; if(k+l>p){ j=(p-k+1)>0?(p-k+1):0; while(k+j<n&&a[k+j+1]==a[j+1])j++; g[k]=j,t=k; }else g[k]=l; } for(i=n;i;i--)g[i]=g[i-1]; for(i=1;i<ans;i++){ j=g[i+1]; if(j==n-i||g[i+2]>=n-i+1)ans=i;else{ j+=i+2,k=(j-2)/i*i+1,t=k+i; if(t>n)t=n; if(hash(j,t)!=hash(j-k,t-k)||g[t+1]<n-t)continue; for(t++;t<=n;t+=i)if(g[t]<min(i,n-t+1))break; if(t>n)ans=i; } } } int main(){ for(pow[0]=i=1;i<N;i++)pow[i]=pow[i-1]*P; scanf("%d",&T); while(T--){ scanf("%d%s",&n,a+1); ans=n-1; solve(); for(i=1;i<n-i+1;i++)swap(a[i],a[n-i+1]); solve(); printf("%d\n",ans); } }
Trial Finals:
Wyznaczanie planu sieci drogowej 2
前$n-2$次每次取出一个度数$=1$的点和一个度数$>1$的点连边;最后一次取出两个度数$=1$的点连边。
#include<cstdio> #include<cstdlib> const int N=2000005; int n,i,x,y,ca,cb,a[N],b[N],f[N],d[N],deg[N]; void NIE(){ puts("BRAK"); std::exit(0); } int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",°[i]); if(deg[i]==1)a[++ca]=i;else b[++cb]=i; } for(i=1;i<n-1;i++){ if(!ca)NIE(); if(!cb)NIE(); x=a[ca--]; y=b[cb--]; f[x]=y; d[x]++; d[y]++; if(d[y]+1<deg[y])b[++cb]=y;else a[++ca]=y; } if(!ca)NIE(); x=a[ca--]; if(!ca)NIE(); y=a[ca--]; f[x]=y; d[x]++; d[y]++; for(i=1;i<=n;i++)if(d[i]!=deg[i])NIE(); for(i=1;i<=n;i++)if(f[i])printf("%d %d\n",i,f[i]); }
Finals:
Computational Biology
对于每个长度为$m$的子串,由其向循环移一位的字符串hash连边,找最大环即可。
时间复杂度$O(qn\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef unsigned long long ull; typedef pair<ull,ull>P; const int N=500005,S=233; int n,q,m,ce,i,j,f[N],vis[N],ans,now; char a[N]; ull p[N],tmp,base; P e[N]; int main(){ scanf("%d%d%s",&n,&q,a+1); for(p[0]=i=1;i<=n;i++)p[i]=p[i-1]*S; while(q--){ scanf("%d",&m); tmp=ce=0,base=p[m]; for(i=1;i<=n;i++){ tmp=tmp*S+a[i]; if(i>m)tmp-=base*a[i-m]; e[++ce]=P(tmp,tmp*S-(base-1)*a[i-m+1]); } sort(e+1,e+ce+1); ans=0; for(i=1;i<=ce;i++)f[i]=0; for(i=1;i<=ce;i=j){ for(j=i;j<=ce&&e[i]==e[j];j++); f[i]=j-i; } for(i=1;i<=ce;i++)if(f[i]){ tmp=e[i].second; now=0; while(1){ j=lower_bound(e+1,e+ce+1,P(tmp,0))-e; if(j<1||j>ce||e[j].first!=tmp||!f[j])break; now+=f[j]; f[j]=0; if(j==i){ if(now>ans)ans=now; break; } tmp=e[j].second; } } printf("%d\n",ans); } }
Byteland Worldbeat Publishers
不妨设$n=m$,考虑一个完美匹配:
- 对于每条匹配边$(左u,右v,w)$,连边$左 u\rightarrow 右 v$,边权$w$。
- 对于每条非匹配边$(左u,右v,w)$,连边$右 v\rightarrow 左 u$,边权$-w$。
那么每个完美匹配权值和相同当且仅当每个环的边权和都是$0$。
注意到所有环都可以由$左u\rightarrow 右 v\rightarrow 左 u+1\rightarrow 右 v+1\rightarrow 左 u$拼成,于是对于所有$(u,v)$检查这样的环边权和是否是$0$即可。
将信息排序后双指针即可完成检查。
时间复杂度$O(k\log k+n+m)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100005,M=300005; int Case,n,m,i,j,k,o,nxt,A,B,C,D,st[N],en[N]; struct E{int x,l,r,v;}e[M]; inline bool cmp(const E&a,const E&b){return a.x==b.x?a.l<b.l:a.x<b.x;} inline void up(int x){ if(x<o||x>nxt)return; nxt=x; } bool check(){ scanf("%d%d",&n,&m); if(n<m)n=m; scanf("%d",&m); for(i=1;i<=m;i++)scanf("%d%d%d%d",&e[i].x,&e[i].l,&e[i].r,&e[i].v); sort(e+1,e+m+1,cmp); for(i=1;i<=n;i++)st[i]=m+1,en[i]=0; for(i=1;i<=m;i++)en[e[i].x]=i; for(i=m;i;i--)st[e[i].x]=i; for(i=1;i<n;i++){ j=st[i],k=st[i+1],o=1; while(o<n){ while(j<=en[i]&&e[j].r<o)j++; while(k<=en[i+1]&&e[k].r<o)k++; A=0; if(j<=en[i]&&e[j].l<=o)A=e[j].v; B=0; if(k<=en[i+1]&&e[k].l<=o)B=e[k].v; o++; while(j<=en[i]&&e[j].r<o)j++; while(k<=en[i+1]&&e[k].r<o)k++; C=0; if(j<=en[i]&&e[j].l<=o)C=e[j].v; D=0; if(k<=en[i+1]&&e[k].l<=o)D=e[k].v; if(A+D!=B+C)return 0; nxt=n; up(n-1); if(j<=en[i]){ up(e[j].l-2); up(e[j].l-1); up(e[j].r-1); up(e[j].r); } if(k<=en[i+1]){ up(e[k].l-2); up(e[k].l-1); up(e[k].r-1); up(e[k].r); } o=nxt; } } return 1; } int main(){ scanf("%d",&Case); while(Case--)puts(check()?"TAK":"NIE"); }
Exam
即求出与每个矩形有交的编号最大的矩形$f[i]$,若$f[i]\leq i$则矩形$i$处于顶层。
枚举矩形$i$和矩形$j$的形状,那么询问范围是二维滑窗,对着$x$扫描线,对着$y$维护线段树即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100010,M=262150; int n,m,A,B,i,j,k,tmp,q[N],id[N],pos[N],v[M],f[N],ans,fin[N]; struct E{int x,y,r,t;}e[N]; inline bool cmpe(const E&a,const E&b){return a.x<b.x;} inline bool cmp(int x,int y){return e[x].y<e[y].y;} void build(int x,int a,int b){ v[x]=0; if(a==b){pos[a]=x;return;} int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); } inline void change(int x,int p){ v[x=pos[x]]=p; for(x>>=1;x;x>>=1)v[x]=max(v[x<<1],v[x<<1|1]); } inline void up(int&a,int b){a<b?(a=b):0;} void ask(int x,int a,int b,int c,int d){ if(e[q[a]].y>=d||e[q[b]].y<=c||!v[x])return; if(e[q[a]].y>c&&e[q[b]].y<d){up(tmp,v[x]);return;} if(a==b)return; int mid=(a+b)>>1; ask(x<<1,a,mid,c,d); ask(x<<1|1,mid+1,b,c,d); } void gao(int me,int him,int xl,int xr,int yl,int yr){ for(m=0,i=1;i<=n;i++)if(e[i].r==him)q[++m]=i; if(!m)return; sort(q+1,q+m+1,cmp); for(i=1;i<=m;i++)id[q[i]]=i; build(1,1,m); for(i=j=k=1;i<=n;i++)if(e[i].r==me){ while(j<=n&&e[j].x<e[i].x+xr){ if(e[j].r==him)change(id[j],e[j].t); j++; } while(k<=n&&e[k].x<=e[i].x+xl){ if(e[k].r==him)change(id[k],0); k++; } tmp=0; ask(1,1,m,e[i].y+yl,e[i].y+yr); up(f[e[i].t],tmp); } } int main(){ scanf("%d%d%d",&n,&A,&B); for(i=1;i<=n;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].r),e[i].t=i; sort(e+1,e+n+1,cmpe); gao(0,0,-B,B,-A,A); gao(1,1,-A,A,-B,B); gao(0,1,-A,B,-B,A); gao(1,0,-B,A,-A,B); for(i=1;i<=n;i++)if(f[i]<=i)fin[++ans]=i; printf("%d\n",ans); for(i=1;i<=ans;i++)printf("%d ",fin[i]); }
Computational Geometry
$n$为奇数则无解,否则$n$的方案可以由$n-2$的方案右边拼上$1\times 2$或$2\times 1$的矩形得到。
#include<cstdio> int n,i,o,x; int main(){ scanf("%d",&n); if(n&1)return puts("NIE"),0; puts("0 0"); puts("0 2"); puts("2 2"); for(i=4,x=2;i<n;i+=2,o^=1){ if(!o){ printf("%d 1\n",x); printf("%d 1\n",x+=2); }else{ printf("%d 2\n",x); printf("%d 2\n",x+=1); } } printf("%d 0",x); }
Coprime Numbers
设$g_i$表示数字$i$倍数的出现次数,$f_i$表示有多少对数字的最大公约数是$i$的倍数,则$f_i=C(g_i,2)-f_{2i}-f_{3i}-\dots$。
时间复杂度$O(a\log a)$。
#include<cstdio> const int M=3000005; int n,m,i,j,x,c[M];long long f[M]; int main(){ scanf("%d",&n); while(n--){ scanf("%d",&x); c[x]++; if(x>m)m=x; } for(i=m;i;i--){ for(j=i;j<=m;j+=i)f[i]+=c[j]; f[i]=1LL*f[i]*(f[i]-1); for(j=i+i;j<=m;j+=i)f[i]-=f[j]; } printf("%lld",f[1]/2); }
Prime prime power
对于$a^b$,如果$b=2$,那么在$[\sqrt{n},\sqrt{n}+k\log k]$内必定能找到$k$个质数作为$a$。
筛出$n^{\frac{1}{4}}$内的所有质数,暴力枚举所有落在该区间内的倍数,将其筛掉,即可判断每个数是否是质数。
然后以最大的质数的平方作为上界,枚举更大的$a$和$b$,这里方案数指数级下降,故暴力即可。
最后排序输出第$k$小的值即可。
时间复杂度$O(n^{\frac{1}{3}}+k\log^2k)$。
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; typedef long long ll; const int N=1050010; ll n,lim,t,o,b[66],q[N*2];int cnt,k,m,i,j,a[66],p[N],tot;bool v[N],vis[N*2]; inline ll pow(ll a,int b){ ll t=1; while(b--){ if(a>lim/t)return lim+1; t*=a; } return t; } inline ll powfast(ll a,int b){ll t=1;for(;b;b>>=1,a*=a)if(b&1)t*=a;return t;} void sieve(ll n){ int i,j; for(tot=0,i=2;i<=n;i++){ if(!v[i])p[tot++]=i; for(j=0;j<tot&&i*p[j]<=n;j++){ v[i*p[j]]=1; if(i%p[j]==0)break; } } } inline void check(int x){for(ll i=max(t/x*x,2LL*x);i<=lim;i+=x)if(i>=t)vis[i-t]=1;} int main(){ scanf("%lld%d",&n,&k); t=max((ll)sqrt(n)-10,2LL); while(t*t<=n)t++; sieve((ll)sqrt(lim=t+max(k,10)*21)+5); for(i=0;i<tot;i++)check(p[i]); for(o=t;o<=lim&&cnt<k;o++)if(!vis[o-t])q[++cnt]=o*o; lim=q[cnt]; for(i=3;;a[++m]=i++)if((1LL<<i)>lim)break; for(b[1]=2;(b[1]+1)*(b[1]+1)*(b[1]+1)<=lim;b[1]++); for(i=2;i<=m;i++)for(b[i]=b[i-1];pow(b[i],a[i])>lim;b[i]--); sieve(max(b[1],1LL*a[m])); for(i=1;i<=m;i++)if(!v[a[i]]){ for(j=0;j<tot&&powfast(p[j],a[i])<=n;j++); for(;j<tot&&p[j]<=b[i];j++)q[++cnt]=powfast(p[j],a[i]); } sort(q+1,q+cnt+1); printf("%lld",q[k]); }
Hard Choice
在每条边两个点中间加上一个虚拟点代表这条边权,就可以化边权为点权。
把没删掉的边用LCT维护一棵生成树,树边都是桥。
对于一条非树边,把树上对应路径上所有边的权值都修改为$0$,表示都不是桥。
然后倒着处理询问,对于每次删掉的边,把两点路径上边权都修改为$0$。
询问等价于查询两点间边权和,若两点连通且路径上不存在桥,则有解。
#include<cstdio> #include<map> const int N=200010,BUF=5000000; char Buf[BUF],*buf=Buf; int a[N],n,m,i,x,fa[N],edge[N][2],ask[N][4],q; struct LCT{int f,son[2],sum,data;bool rev,tag;}T[N]; int father(int x){return fa[x]==x?x:fa[x]=father(fa[x]);} std::map<int,bool>del[N>>1]; inline void swap(int&a,int&b){int c=a;a=b;b=c;} inline bool isroot(int x){return !T[x].f||T[T[x].f].son[0]!=x&&T[T[x].f].son[1]!=x;} inline void rev1(int x){if(!x)return;swap(T[x].son[0],T[x].son[1]),T[x].rev^=1;} inline void makezero1(int x){if(!x)return;T[x].sum=T[x].data=0;T[x].tag=1;} inline void pb(int x){ if(T[x].rev)rev1(T[x].son[0]),rev1(T[x].son[1]),T[x].rev=0; if(T[x].tag)makezero1(T[x].son[0]),makezero1(T[x].son[1]),T[x].tag=0; } inline void up(int x){T[x].sum=T[x].data|T[T[x].son[0]].sum|T[T[x].son[1]].sum;} inline void rotate(int x){ int y=T[x].f,w=T[y].son[1]==x; T[y].son[w]=T[x].son[w^1]; if(T[x].son[w^1])T[T[x].son[w^1]].f=y; if(T[y].f){ int z=T[y].f; if(T[z].son[0]==y)T[z].son[0]=x;else if(T[z].son[1]==y)T[z].son[1]=x; } T[x].f=T[y].f;T[x].son[w^1]=y;T[y].f=x;up(y); } inline void splay(int x){ int s=1,i=x,y;a[1]=i; while(!isroot(i))a[++s]=i=T[i].f; while(s)pb(a[s--]); while(!isroot(x)){ y=T[x].f; if(!isroot(y)){if((T[T[y].f].son[0]==y)^(T[y].son[0]==x))rotate(x);else rotate(y);} rotate(x); } up(x); } inline void access(int x){for(int y=0;x;y=x,x=T[x].f)splay(x),T[x].son[1]=y,up(x);} inline void makeroot(int x){access(x);splay(x);rev1(x);} inline void link(int x,int y){makeroot(x);T[x].f=y;access(x);} inline void makezero(int x,int y){ if(father(x)!=father(y)){ fa[father(x)]=father(y); n++; T[n].sum=T[n].data=1; link(x,n);link(n,y); return; } makeroot(x); access(y); splay(x); makezero1(x); } inline int getsum(int x,int y){ if(father(x)!=father(y))return 1; makeroot(x); access(y); splay(x); return T[x].sum; } inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;} int main(){ fread(Buf,1,BUF,stdin);read(n);read(m);read(q); for(i=1;i<=n;i++)fa[i]=i; for(i=1;i<=m;i++){ read(edge[i][0]);read(edge[i][1]); if(edge[i][0]>edge[i][1])swap(edge[i][0],edge[i][1]); } for(i=1;i<=q;i++){ while(*buf!='Z'&&*buf!='P')buf++; ask[i][0]=x=*buf=='P',buf++; read(ask[i][1]);read(ask[i][2]); if(ask[i][1]>ask[i][2])swap(ask[i][1],ask[i][2]); if(!x)del[ask[i][1]][ask[i][2]]=1; } for(i=1;i<=m;i++)if(!del[edge[i][0]][edge[i][1]])if(father(edge[i][0])!=father(edge[i][1])){ fa[father(edge[i][0])]=father(edge[i][1]); n++; T[n].sum=T[n].data=1; link(edge[i][0],n);link(n,edge[i][1]); del[edge[i][0]][edge[i][1]]=1; } for(i=1;i<=m;i++)if(!del[edge[i][0]][edge[i][1]])makezero(edge[i][0],edge[i][1]); for(i=q;i;i--)if(!ask[i][0])makezero(ask[i][1],ask[i][2]);else ask[i][3]=getsum(ask[i][1],ask[i][2]); for(i=1;i<=q;i++)if(ask[i][0])puts(ask[i][3]?"NIE":"TAK"); }