题目大意:
给你一张n个结点(1<=n<=250)的有向图,m(1<=m<=25000)组操作,每次翻转一些边,求每组操作后可以相互到达的点的对数。
解题思路:
很容易就可以发现,每个点数为t的强连通分量对答案的贡献为t(t-1)/2。那么我们只要每次找到各个强连通分量即可。由于这里存在边的翻转操作,所以我们只能使用邻接矩阵来表示图。对于邻接矩阵表示的图Kosaraju算法的时间复杂度为O(n^2),那么总复杂度为O(m*n*n),这样会T。面对稠密图,Kosaraju算法的瓶颈在于寻找与点xx相连且未访问过的点。gx由于超出时间限制不是很多,我们就可以用bitset优化,使时间复杂度降到O(m*n*n/64)就可以通过了。
下面代码使用了gcc的__builtin_函数,这里就简单介绍__builtin_函数中处理二进制位的函数:
int __builtin_ffs (unsigned x) 返回x中最后一个1是从右往左第几位
int __builtin_popcount (unsigned x) 返回x中1的个数
int __builtin_ctz (unsigned x) 返回x末尾0的个数(x等于0时未定义)
int __builtin_clz (unsigned x) 返回x中前导0的个数(x等于0时未定义)
int __builtin_parity (unsigned x) 返回x中1的奇偶性
AC代码:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <ctime> #include <vector> #include <queue> #include <stack> #include <deque> #include <string> #include <map> #include <set> #include <list> using namespace std; #define INF 0x3f3f3f3f3f3f3f3f #define LL long long #define fi first #define se second #define mem(a,b) memset((a),(b),sizeof(a)) const int MAXV=250+3; struct Bitset//用unsigned数组实现bitset,为了使用__builtin_ctz() { unsigned v[8];//8个unsigned表示8*32=256位 void reset()//清零 { for(int i=0;i<8;++i) v[i]=0; } void set(int x)//把某一位设为1 { v[x>>5]|=1u<<(x&31); } void flip(int x)//翻转某一位 { v[x>>5]^=1u<<(x&31); } bool test(int x)//返回某一位是否为1 { return v[x>>5]>>(x&31)&1; } }vis, G[MAXV], rG[MAXV];//访问标记,原图,反向图 int V, M; int ord[MAXV];//dfs序 int num;//当前强连通分量的结点数 inline void flipedge(int x, int y)//翻转一条边 { G[x].flip(y); rG[y].flip(x); } void dfs0(int x)//正向dfs { vis.flip(x); for(int i=0;i<8;++i) while(true) { unsigned tmp=vis.v[i]&G[x].v[i];//得到相连的没有访问过的点的状压 if(!tmp) break; dfs0(i<<5|__builtin_ctz(tmp));//下一个相连的没有访问过的点 } ord[++ord[0]]=x; } void dfs1(int x)//反向dfs { vis.flip(x); ++num; for(int i=0;i<8;++i) while(true) { unsigned tmp=vis.v[i]&rG[x].v[i]; if(!tmp) break; dfs1(i<<5|__builtin_ctz(tmp)); } } int kosaraju()//每次查询的答案 { vis.reset(); int res=0; ord[0]=0; for(int i=0;i<V;++i) vis.set(i); for(int i=0; i<V;++i) if(vis.test(i)) dfs0(i); for(int i=0;i<V;++i) vis.set(i); for(int i=V;i;--i) if(vis.test(ord[i]))//找到一个新的强连通分量 { num=0; dfs1(ord[i]); res+=num*(num-1)/2; } return res; } int main() { int T_T; scanf("%d",& T_T); while(T_T--) { scanf("%d%d",&V, &M); for(int i=0;i<V;++i) { G[i].reset(); rG[i].reset(); } for(int i=0;i<V;++i) { char s[MAXV]; scanf("%s", s); for(int j=0;j<V;++j) if(s[j]=='1') flipedge(i, j); } while(M--) { int n; scanf("%d", &n); while(n--) { int a, b; scanf("%d%d", &a, &b); flipedge(a-1, b-1); } printf("%d\n", kosaraju()); } } return 0; }