[2020-2021 集训队作业] Tom & Jerry

· · 题解

传送门

首先考虑两人的策略是什么。

Jerry 的行动较为复杂,不好考虑策略。

Tom 行动比较简单,不妨从 Tom 的策略入手。

整体感受一下,Tom 为了抓住 Jerry,所以会朝着 Jerry 的方向一直走。

建出圆方树,在树上考虑。

若 Tom 当前能走到一个距离 Jerry 更近的点,则一定往这个方向走,否则,Tom 在不会远离 Jerry 的前提下走向度数最大的点,这是为了处理 Jerry 始终与 Tom 距离为 1 的情况。

所以可以让 Tom 使用如下策略:若可以接近 Jerry,则往 Jerry 的方向走。否则走向不会远离 Jerry 的度数最大的点。

考虑图论建模,用点 (a,b) 表示 Tom 在 a,Jerry 在 b,轮到 Jerry 这个状态。

在新图上跑 SCC,则 Tom 能赢的充要条件为当前状态无法到达一个大小大于 $1$ 的 SCC。 可以发现,很多状态是可以合并的。 由于 Jerry 可以走任意步,所以不妨设 $(x,y)$ 表示 Tom 在 $x$,Jerry 可以到达以 $x$ 为根时 $y$ 的子树内的任意点。 此时总点数为 $O(n)$,边数 $O(n^2)$,但很难卡满。 瓶颈在于给一个点除掉一个儿子后的其他儿子连边。 可以使用经典技巧,新建虚点表示给前后缀连边,这样边数 $O(n)$,总复杂度 $O((n+q)\log n)$。 使用哈希表和长剖求 k 级祖先等精细实现可能可以做到 $O(n+q)$。 ```cpp #include <bits/stdc++.h> using namespace std; const int N=1e6+5; int n,m,qs,dfn[N],dd[N],rd[N],f[N],siz[N],bh[N],sms,cnt,low[N],eds[N],idx,tot,fa[N][20],mk[N],dep[N]; vector<int>e[N],st,p[N],g[N],b[N],to[N]; unordered_map<int,int>dy[N],wz[N],dds[N]; vector<int>qz[N],hz[N]; deque<int>q; void tarjan(int x){ st.push_back(x); dfn[x]=low[x]=++idx; for(auto c:e[x]){ if(!dfn[c]){ tarjan(c); low[x]=min(low[x],low[c]); if(dfn[x]<=low[c]){ int y;cnt++; do{ y=st.back(); st.pop_back(); p[cnt].push_back(y); p[y].push_back(cnt); }while(y!=c); p[cnt].push_back(x); p[x].push_back(cnt); } }else low[x]=min(low[x],dfn[c]); } } void dfs1(int x){ mk[x]=1;dfn[x]=++idx; for(int i=1;i<=19;i++)fa[x][i]=fa[fa[x][i-1]][i-1]; for(auto c:p[x]){ if(mk[c])continue; fa[c][0]=x; dep[c]=dep[x]+1; dfs1(c); } eds[x]=idx; mk[x]=0; } void dfs2(int x){ dd[x]=low[x]=++idx; st.push_back(x); mk[x]=1; for(auto c:b[x]){ if(!dd[c]){ dfs2(c); low[x]=min(low[x],low[c]); }else if(mk[c])low[x]=min(low[x],dd[c]); } if(dd[x]==low[x]){ int y;sms++; do{ y=st.back(); st.pop_back(); bh[y]=sms;mk[y]=0; siz[sms]++; }while(y!=x); } } int gm(int a,int b){ if(fa[a][0]==fa[b][0])return fa[a][0]; if(fa[a][1]==b)return fa[a][0]; return fa[b][0]; } int up(int x,int k){ while(k){ x=fa[x][__lg(k&-k)]; k^=k&-k; } return x; } signed main(){ ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); cin>>n>>m>>qs; cnt=n; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; e[x].push_back(y); e[y].push_back(x); } tarjan(1); for(int i=1;i<=n;i++){ for(auto c:p[i])dy[i][c]=++tot; qz[i].resize(p[i].size()); hz[i].resize(p[i].size()); for(int j=0;j<p[i].size();j++){ int c=p[i][j]; wz[i][c]=j; qz[i][j]=++tot; hz[i][j]=++tot; if(j)b[qz[i][j]].push_back(qz[i][j-1]); b[qz[i][j]].push_back(dy[i][c]); b[hz[i][j]].push_back(dy[i][c]); } for(int j=0;j+1<p[i].size();j++)b[hz[i][j]].push_back(hz[i][j+1]); } idx=0; dfs1(1); for(int i=1;i<=n;i++){ for(auto c:e[i]){ int tmp=gm(i,c); dds[i][tmp]++; } } for(int i=1;i<=n;i++){ for(auto c:e[i]){ int tmp=gm(i,c); g[tmp].push_back(c); } for(auto c:p[i]){ sort(g[c].begin(),g[c].end(),[&](int a,int b){ return dds[a][c]>dds[b][c]; }); int tmp=dy[i][c]; for(auto d:g[c]){ int k=wz[d][c]; if(k)b[tmp].push_back(qz[d][k-1]); if(k+1<p[d].size())b[tmp].push_back(hz[d][k+1]); } if(g[c].size()<p[c].size()-1){ b[tmp].push_back(dy[g[c][0]][c]); } g[c].clear(); } } idx=0; for(int i=1;i<=tot;i++)if(!dd[i])dfs2(i); for(int i=1;i<=tot;i++){ for(auto c:b[i]){ if(bh[i]==bh[c])continue; to[bh[c]].push_back(bh[i]); rd[bh[i]]++; } if(siz[i]>1)f[i]=1; } for(int i=1;i<=tot;i++)if(!rd[i])q.push_back(i); while(q.size()){ int x=q.front(); q.pop_front(); for(auto c:to[x]){ rd[c]--; f[c]|=f[x]; if(!rd[c])q.push_back(c); } } while(qs--){ int a,b; cin>>a>>b; if(dfn[b]>=dfn[a]&&dfn[b]<=eds[a]){ b=up(b,dep[b]-dep[a]-1); }else{ b=fa[a][0]; } if(f[bh[dy[a][b]]])cout<<"No\n"; else cout<<"Yes\n"; } } ```