BZOJ2208 [Jsoi2010]连通数[缩点/Floyd传递闭包+bitset优化]
显然并不能直接dfs,因为$m$会非常大,复杂度就是$O(mn)$;
这题有三种做法,都用到了bitset的优化。第二种算是一个意外的收获,之前没想到竟然还有这种神仙操作。。
方法一:缩点+DAG上bitset优化的统计
做有向图连通问题上来先看可不可以缩点首先一个环内点是可以相互连通的,又发现DAG也许方便统计,于是缩点。。然后变成一张DAG,只要统计每个点可以往后走到的所有点权(指该环包含的点数)的和。并不好直接把后继全加上去,因为会有点被重复统计。为了避免重复统计,只要直接每个点开一个bitset,表示他能走到哪些点,这样dp的时候直接对儿子取or就可以了,然后再统计,不会重复。复杂度$O(\frac{mn}{32})$。实际上是可以卡过去的。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #include<bitset> 8 #define mst(x) memset(x,0,sizeof x) 9 #define dbg(x) cerr << #x << " = " << x <<endl 10 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 11 using namespace std; 12 typedef long long ll; 13 typedef double db; 14 typedef pair<int,int> pii; 15 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 16 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 17 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 18 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 19 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 20 template<typename T>inline T read(T&x){ 21 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 22 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 23 } 24 const int N=2000+3; 25 struct thxorz{ 26 int to[N*N],nxt[N*N],head[N],tot; 27 inline void add(int x,int y){to[++tot]=y,nxt[tot]=head[x],head[x]=tot;} 28 }G1,G2; 29 int from[N*N]; 30 char s[N]; 31 int n,m,ans; 32 #define y G1.to[j] 33 int dfn[N],low[N],tim,stk[N],instk[N],bel[N],sum[N],val[N],Top; 34 void tarjan(int x){ 35 dfn[x]=low[x]=++tim,stk[++Top]=x,instk[x]=1; 36 for(register int j=G1.head[x];j;j=G1.nxt[j]){ 37 if(!dfn[y])tarjan(y),MIN(low[x],low[y]); 38 else if(instk[y])MIN(low[x],dfn[y]); 39 } 40 if(dfn[x]==low[x]){ 41 int tmp; 42 do instk[tmp=stk[Top--]]=0,bel[tmp]=x,++sum[x];while(tmp^x); 43 } 44 } 45 #undef y 46 #define y G2.to[j] 47 bitset<N> S[N]; 48 void dp(int x){ 49 if(val[x])return; 50 S[x][x]=1; 51 for(register int j=G2.head[x];j;j=G2.nxt[j])dp(y),S[x]|=S[y]; 52 for(register int i=1;i<=n;++i)if(S[x][i])val[x]+=sum[i]; 53 val[x]=val[x]*sum[x]; 54 } 55 #undef y 56 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 57 read(n); 58 for(register int i=1;i<=n;++i){ 59 scanf("%s",s+1); 60 for(register int j=1;j<=n;++j)if(i^j&&s[j]=='1')G1.add(i,j),from[G1.tot]=i; 61 } 62 for(register int i=1;i<=n;++i)if(!dfn[i])tarjan(i); 63 for(register int t=1,x,y;t<=G1.tot;++t){ 64 x=from[t],y=G1.to[t]; 65 if(bel[x]^bel[y])G2.add(bel[x],bel[y]); 66 } 67 for(register int i=1;i<=n;++i)dp(i),ans+=val[i]; 68 printf("%d\n",ans); 69 return 0; 70 }
方法二:Floyd传递闭包+bitset优化
这个优化真是妙啊,之前学Floyd都没想过这个优化。`````
连通性可以用二元逻辑关系表示,也就是可以用Floyd来做传递闭包。具体来说,就是设$f_{i,j}=0/1$表示$i$到$j$是否可达。然后转移的话是$\text{f[i][j]|=f[i][k]&f[k][j]}$。
注意这个转移。$f_{i,j}$相当于看成对于$i$点,用一个bitset表示他到$1\sim n$各点的可达性。
再观察,$f[i][k]$值已经是定的了,如果是$0$就不转移了,是$1$的话,对于每一位$j$,只要用$f[k]$对应的$j$位or一下就可以了。并且$f[i][k]$在本次转移中是不会变的。
所以,直接用bitset整体进行or操作就行了。$O(\frac{n^3}{32})$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #include<bitset> 8 #define mst(x) memset(x,0,sizeof x) 9 #define dbg(x) cerr << #x << " = " << x <<endl 10 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 11 using namespace std; 12 typedef long long ll; 13 typedef double db; 14 typedef pair<int,int> pii; 15 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 16 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 17 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 18 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 19 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 20 template<typename T>inline T read(T&x){ 21 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 22 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 23 } 24 const int N=2000+5; 25 bitset<N> f[N]; 26 int n,ans; 27 char s[N]; 28 29 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 30 read(n); 31 for(register int i=1;i<=n;++i){ 32 scanf("%s",s+1); 33 for(register int j=1;j<=n;++j)f[i][j]=s[j]=='1'||i==j; 34 } 35 for(register int k=1;k<=n;++k) 36 for(register int i=1;i<=n;++i) 37 if(f[i][k])f[i]|=f[k]; 38 for(register int i=1;i<=n;++i)ans+=f[i].count(); 39 return printf("%d\n",ans),0; 40 }
方法三:暴力BFS+bitset标记松弛点(手写警告)
对每一个点,直接开始BFS,然后这个做法时间瓶颈在于每次都要枚举到所有边,那么我么尽量让他BFS时候从松弛点去走没有被访问过的点,避免走路径通向访问过的点的。可以用bitset维护目前所有点哪些没有被访问过,每个节点再开一个bitset表示他通往哪些点,每次BFS松弛的时候,把两者and一下,这样所有的$1$位就是没有访问过的,仅取出这些$1$,访问之即可。这样,就使得走过的边数量与点数相同,复杂度就降成了$O(n(\frac{n^2}{32}+n))$。不过,这种方法由于要取出$1$,如果直接对bitset每一位直接检验,复杂度就又回去了,而STL的bitset太垃圾没有这个取$1$复杂度和$1$数量相同的方法,所以我们要手写bitset。。。这种方法是yql讲过的,由于我太菜了,所以我不会写,实际要咕咕咕。