CSP-S 2022 T3 题解
简述题意
一张 \(n\) 个点,\(m\) 条边的有向图,有 \(q\) 次操作,每次删掉一条已有的边,恢复一条被删的边,删掉一个点的所有入边,回复一个点的所有入边。每次操作后询问此时图是不是一个内向基环树森林。
数据范围:\(1\le n,m,q\le 5\times10^5\)。
解题思路
15pts
我会 spfa 判正环!(希望我是唯一一个考场上这样写的人。)复杂度为 \(O(n^2q)\)。
40pts
直接 DFS 就可以判环了,按题意模拟即可。复杂度为 \(O(nq)\)。
60pts
考虑基环树的性质:基环树有 \(n\) 个点,\(n\) 条边。
我们不难证明每个点出度为 \(1\) 的图就是内向基环树森林。
直接维护每个点的出度即可,可以过掉有特殊性质的点,但还是 \(O(nq)\) 的。
100pts
动态内向基环树森林的判断不弱于动态仙人掌,这种东西显然不是 CSP 该考的。发现我们只需要判断,于是可以想到 Hash。
我们给每个点一个 \([0,V)\cap\mathbb{Z}\) 内均匀随机的权值 \(a_i\),定义一个点的点权为 \(v_t=\sum\limits_{s\rightarrow t\in E}a_s\),如果操作完后有 \(\sum v_i=\sum a_i\),我们就认为该图是内向基环树森林。(所有运算均在 \(\bmod V\) 的意义下进行。)
设一个点的出度为 \(o_i\),记 \(S=\sum o_ia_i\),打表发现 \(S\) 在值域内大概是均匀分布的,于是 Hash 冲突的概率大约就是 \(O(\frac{1}{V})\)。利用 unsigned long long
自然溢出实测可过。
代码
给出主要实现:
const int N=5e5+5;
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
ull val[N],have[N],all[N],now,tmp;
int n,m,q;
inline void add(int x,ull k) { now+=k,have[x]+=k; }
signed main()
{
read_(n,m);
irep(i,1,n) val[i]=rnd(),tmp+=val[i];
for(int i=1,u,v;i<=m;++i) read_(u,v),all[v]+=val[u],add(v,val[u]);
read_(q);
for(int i=1,op,u,v;i<=q;++i)
{
read_(op);
if(op&1) read_(u,v);
else read_(u);
switch(op)
{
case 1: add(v,-val[u]); break;
case 2: add(u,-have[u]); break;
case 3: add(v,val[u]); break;
case 4: add(u,all[u]-have[u]); break;
} write_(now==tmp?"YES\n":"NO\n");
}
return 0;
}
个人认为这题思维难度比较大,毕竟 CSP/NOIP 没有怎么考过随机化技巧,比较考察平时对题型的积累。