首先有向图的题目不难想到先tarjan缩点
一个强连通分量中的点的连通数显然是相等;
据说这样直接dfs就可以过了,但显然不够精益求精
万一给定的是一个完全的DAG图怎么办,dfs铁定超时;
首先想,dfs进行了很多不必要的操作,比如说i--->j
那么j的连通数一定也是i的连通数,但我们做dfs是需要做两遍的,降低了效率
那么为了提高效率,我们希望支持一个这样的操作
记录下每个点所能到的点,并且能快速的合并;
不由的想到位运算,但是最多只有30位,而实际有2000个点怎么办?
那我们就维护最多70个数,每个数表示到达情况
dp[k,i]为一个表示连通状况的数
第i个数的二进制上第j个位置(位置从右往左,0~30) 代表点k能否到达点(i-1)*30+j+1 (1代表可到达,0代表不可)
然后从出度为0的点不断dp即可;
具体见程序,表达不清,时间复杂度大约是O(nm/30) 还是非常优秀的
1 type link=^node; 2 node=record 3 po:longint; 4 next:link; 5 end; 6 7 var edge,way:array[0..2010] of link; 8 v,f:array[0..2010] of boolean; 9 be,count,dfn,low,st:array[0..2010] of longint; 10 dp:array[0..2200,0..70] of longint; 11 ans,s,state,h,t,l,x,y,i,j,n,m:longint; 12 ch:ansistring; 13 p:link; 14 15 function min(a,b:longint):longint; 16 begin 17 if a>b then exit(b) else exit(a); 18 end; 19 20 procedure add(y:longint;var q:link); 21 var p:link; 22 begin 23 new(p); 24 p^.po:=y; 25 p^.next:=q; 26 q:=p; 27 end; 28 29 procedure tarjan(x:longint); 30 var y:longint; 31 p:link; 32 begin 33 p:=edge[x]; 34 v[x]:=true; 35 f[x]:=true; 36 inc(h); 37 dfn[x]:=h; 38 low[x]:=h; 39 inc(t); 40 st[t]:=x; 41 while p<>nil do 42 begin 43 y:=p^.po; 44 if not v[y] then 45 begin 46 tarjan(y); 47 low[x]:=min(low[x],low[y]); 48 end 49 else if f[y] then 50 low[x]:=min(low[x],low[y]); 51 p:=p^.next; 52 end; 53 if low[x]=dfn[x] then 54 begin 55 inc(s); 56 while st[t+1]<>x do 57 begin 58 y:=st[t]; 59 f[y]:=false; 60 be[y]:=s; 61 inc(count[s]); 62 dec(t); 63 end; 64 end; 65 end; 66 67 procedure merge(x,y:longint); //合并点的连通情况 68 var i,j:longint; 69 begin 70 for i:=1 to state do 71 dp[x,i]:=dp[x,i] or dp[y,i]; 72 end; 73 74 function get(x:longint):longint; 75 var i,j,r:longint; 76 begin 77 get:=0; 78 for i:=1 to state do //穷举每个数 79 for j:=0 to 29 do //穷举二进制的每一位 80 begin 81 r:=1 shl j; //位运算的技巧 82 if r>dp[x,i] then break; 83 if (dp[x,i] and r)<>0 then 84 get:=get+count[(i-1)*30+j+1]; 85 end; 86 end; 87 88 begin 89 readln(n); 90 for i:=1 to n do 91 begin 92 readln(ch); 93 for j:=1 to n do 94 begin 95 x:=ord(ch[j])-48; 96 if x<>0 then add(j,edge[i]); 97 end; 98 end; 99 for i:=1 to n do 100 if not v[i] then 101 begin 102 h:=0; 103 t:=0; 104 tarjan(i); 105 end; 106 107 fillchar(dfn,sizeof(dfn),0); 108 for i:=1 to n do 109 begin 110 p:=edge[i]; 111 while p<>nil do 112 begin 113 y:=p^.po; 114 if be[y]<>be[i] then 115 begin 116 inc(dfn[be[i]]); //计算出度 117 add(be[i],way[be[y]]); //缩点后记录点be[y]被那些点指向 118 end; 119 p:=p^.next; 120 end; 121 end; 122 fillchar(v,sizeof(v),false); 123 t:=0; 124 for i:=1 to s do 125 edge[i]:=nil; 126 for i:=1 to s do 127 begin 128 p:=way[i]; 129 while p<>nil do 130 begin 131 x:=p^.po; 132 add(i,edge[x]); //记录点i指向那些点 133 p:=p^.next; 134 end; 135 x:=(i-1) div 30+1; 136 y:=(i-1) mod 30; 137 dp[i,x]:=1 shl y; //每个点对自己都是可达的 138 end; 139 for i:=1 to s do 140 if dfn[i]=0 then //从出度为0的点开始dp 141 begin 142 inc(t); 143 st[t]:=i; 144 end; 145 146 state:=s div 30+1; 147 l:=0; 148 while l<=t do 149 begin 150 inc(l); 151 x:=st[l]; 152 p:=edge[x]; 153 while p<>nil do 154 begin 155 y:=p^.po; 156 merge(x,y); //每个x指向的点的连通数一定也是x的连通数,合并 157 p:=p^.next; 158 end; 159 ans:=ans+count[x]*get(x); //计算连通数 160 p:=way[x]; 161 while p<>nil do //类似拓扑排序,删除点x,寻找新的出度为0的点 162 begin 163 y:=p^.po; 164 dec(dfn[y]); 165 if dfn[y]=0 then 166 begin 167 inc(t); 168 st[t]:=y; 169 end; 170 p:=p^.next; 171 end; 172 end; 173 writeln(ans); 174 end.