CF639F Bear and Chemistry
把原始的图进行缩点, 形成边双树,然后把询问和加边的关键点加入,建成虚树.
在虚树上连新加入的边,再跑一遍边双算法.
最后只需查询任意两点是否都在一个联通分量里就行.
细节比较繁琐,然后虚树和点双都要注意对变量的清空.
更简单的做法是用 $\mathrm{LCT}$ 来做.
具体做法就是先将初始图的边双树建出,然后每次询问时进行覆盖.
若两个点之间的路径全部被覆盖,则一定合法.
但是这个需要用边权 $\mathrm{LCT}$ 来做,也有一些细节.
#include <cstdio> #include <cstdio> #include <cstring> #include <stack> #include <vector> #include <algorithm> // 边双. // 跑一个 tarjan 缩点,形成一个树. // 每次把关键点提出建立虚树,并连边. // 在虚树上连非树边,然后对虚树(虚图)跑一遍 tarjan 求点双. #define N 100009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n,m,Q; vector<int>G[N]; struct Tarjan { stack<int>S; vector<int>poi, node[N]; int scc, id, edges, hd[N],to[N<<1],nex[N<<1],low[N], idx[N], dfn[N]; void init() { edges = 1, id=0, scc=0; while(!S.empty()) S.pop(); } void add(int u, int v) { nex[++edges]=hd[u]; hd[u]=edges; to[edges]=v; } void ADD(int x) { poi.pb(x); } void ins(int x, int y) { poi.pb(x); poi.pb(y); low[x]=dfn[x]=low[y]=dfn[y]=0; add(x, y), add(y, x); } void tarjan(int x, int ff) { S.push(x); dfn[x]=low[x]=++scc; for(int i=hd[x];i;i=nex[i]) { int v=to[i]; if(i == ff) continue; if(!dfn[v]) { tarjan(v, i ^ 1); low[x]=min(low[x], low[v]); } else if(!idx[v]) low[x]=min(low[x], dfn[v]); } if(dfn[x] == low[x]) { ++id; for( ; ; ) { int p = S.top(); S.pop(); idx[p] = id; node[id].pb(p); if(p == x) break ; } } } // void solve() { for(int i=0;i<poi.size();++i) { int x=poi[i]; if(!dfn[x]) { tarjan(x, 0); } } } void CLR() { for(int i=0;i<poi.size();++i) { int x=poi[i]; low[x]=dfn[x]=idx[x]=hd[x]=0; } poi.clear(); for(int i=1;i<=edges;++i) { nex[i]=to[i]=0; } edges=1, scc=id=0; while(!S.empty()) S.pop(); } }T, H; struct UFS { int p[N]; void init() { for(int i=0;i<N;++i) p[i]=i; } int find(int x) { return p[x]==x?x:p[x]=find(p[x]); } void merge(int x, int y) { x=find(x); y=find(y); if(x!=y) p[x]=y; } int query(int x, int y) { return find(x) == find(y); } }ufs; // st[x]: x 的 dfs 序. int sta[N], top; int dep[N], vis[N],fa[20][N], arr[N], st[N], cnt , num; void dfs(int x, int ff) { vis[x]=1; fa[0][x]=ff; dep[x]=dep[ff]+1; st[x] = ++ cnt; for(int i=1;i<20;++i) fa[i][x]=fa[i-1][fa[i-1][x]]; for(int i=0;i<G[x].size();++i) { int v=G[x][i]; if(v==ff) continue; dfs(v, x); } } int get_lca(int x, int y) { if(dep[x] > dep[y]) swap(x, y); if(dep[x] != dep[y]) { for(int i=19;i>=0;--i) { if(dep[fa[i][y]] >= dep[x]) y = fa[i][y]; } } if(x == y) return x; for(int i=19;i>=0;--i) { if(fa[i][x] != fa[i][y]) { x = fa[i][x]; y = fa[i][y]; } } return fa[0][x]; } bool cmp(int i, int j) { return st[i] < st[j]; } void ins(int x) { H.ADD(x); if(top <= 1) { sta[++top] = x; return ; } int lca = get_lca(sta[top], x); if(lca == sta[top]) { // sta[top] -> x sta[++top] = x; } else { while(top > 1 && dep[sta[top - 1]] >= dep[lca]) { H.ins(sta[top - 1], sta[top]); -- top; } if(sta[top] != lca) { H.ins(lca, sta[top]); sta[top] = lca; } sta[++top] = x; } } struct Edge { int x, y; Edge(int x = 0, int y = 0): x(x), y(y){} }; vector<Edge>edge; vector<int>oo; int solve() { sort(arr+1, arr+1+num, cmp); num = unique(arr+1, arr+1+num) - arr - 1; for(int i=1;i<=num;++i) { if(i == 1) { top = 0, ins(arr[1]); } else { if(ufs.query(arr[i-1], arr[i])) { ins(arr[i]); } else { while(top > 1) H.ins(arr[top - 1], arr[top]), --top; top = 0, ins(arr[i]); } } } while(top > 1) H.ins(arr[top - 1], arr[top]), --top; // 建立完毕虚树. // (x, y) for(int i=0;i<edge.size();++i) { Edge u = edge[i]; int x = u.x; int y = u.y; H.ins(x, y); // 在虚树中将 (x, y) 加入进去. } H.solve(); // 然后就看是否在一起啥的就可以了. int flag=0; for(int i=1;i<oo.size();++i) { if(H.idx[oo[i]] != H.idx[oo[i-1]]) { flag = 1; break ; } } return flag; } int rotate(int x, int key) { x = (x + key) % n ; if(x == 0) x = n; return x; } int main() { // setIO("input"); scanf("%d%d%d",&n,&m,&Q); T.init(); for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); if(x==y) continue; T.ins(x, y); } // T: tarjan. T.solve(); // T: 第一个. ufs.init(); for(int i=1;i<=T.id;++i) { for(int j = 0; j < T.node[i].size() ; ++ j) { int x = T.node[i][j]; for(int j=T.hd[x];j;j=T.nex[j]) { int v = T.to[j]; if(T.idx[v] == i) continue; G[i].pb(T.idx[v]); ufs.merge(i, T.idx[v]); } } } // G: 边双树. // 开始对 G 建立虚树. for(int i=1;i<=T.id; ++ i) { if(!vis[i]) { dfs(i, 0); } } int R = 0; for(int i=1;i<=Q;++i) { int s1, s2; scanf("%d%d",&s1,&s2),num=0; for(int j=1;j<=s1;++j) { ++num; scanf("%d",&arr[num]); arr[num] = rotate(arr[num], R); arr[num] = T.idx[arr[num]]; oo.pb(arr[num]); } for(int j=1;j<=s2;++j) { int x, y; scanf("%d%d",&x,&y); x=rotate(x, R); y=rotate(y, R); x = T.idx[x]; y = T.idx[y]; edge.pb(Edge(x, y)); ++num; arr[num] = x; ++num; arr[num] = y; } int p = solve(); if(p) { printf("NO\n"); } else { printf("YES\n"), R += i ; } H.CLR(); } return 0; }