2208: [Jsoi2010]连通数
题目描述
解题报告
这题的题目描述非常清晰,看到网上有dalao用 bitset 优化 Floyd 就过了。博主用 tarjan 缩点然后用 bitset 优化传递闭包就可以。
但是看到很多人缩点完刷拓扑啊,其实这个操作很鸡肋。学过 2−sat 应该有所了解,拓扑序就是 tarjan 刷出来的 scc 序(画个图就知道了)。
然后最后刷答案的时候注意一下就可以了,我开了两个数组,一个是或起来之后当前节点的状态,一个是原来的状态,这样的操作使调用
bitset
的次数变多,所以效率很低(
3000ms+
)。
希望各位dalao能告诉我更优的方法。
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int maxn=2005,maxm=4000005;
int tot[2],lnk[maxn][2],son[maxm][2],nxt[maxm][2],x[maxn];
int n,top,s[maxn],G,scc[maxn],low[maxn],dfn[maxn],tim,ans;
bool ins[maxn],vis[maxn][maxn];
bitset<maxn> a[maxn],b[maxn];
void add(int x,int y,int id){
nxt[++tot[id]][id]=lnk[x][id];lnk[x][id]=tot[id];son[tot[id]][id]=y;
}
inline char _read(){
char ch=getchar();
while (ch!='0'&&ch!='1') ch=getchar();
return ch;
}
void tarjan(int x){
dfn[x]=++tim;low[x]=tim;
s[++top]=x;ins[x]=1;
for (int j=lnk[x][0];j;j=nxt[j][0])
if (!dfn[son[j][0]]){tarjan(son[j][0]);low[x]=min(low[x],low[son[j][0]]);}
else if (ins[son[j][0]]) low[x]=min(low[x],dfn[son[j][0]]);
if (dfn[x]==low[x]){
G++;
while (s[top]!=x) scc[s[top]]=G,ins[s[top]]=0,a[G][s[top--]]=1;
scc[x]=G;ins[x]=0;a[G][x]=1;top--;
}
}
void maker(){
for (int i=1;i<=n;i++)
for (int j=lnk[i][0];j;j=nxt[j][0])
if (scc[i]>scc[son[j][0]]&&!vis[scc[i]][scc[son[j][0]]])
add(scc[i],scc[son[j][0]],1),vis[scc[i]][scc[son[j][0]]];
}
int main(){
freopen("exam.in","r",stdin);
freopen("exam.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (_read()=='1') add(i,j,0);
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
for (int i=1;i<=G;i++) x[i]=a[i].count();
maker();
for (int i=1;i<=G;i++) b[i]=a[i];
for (int i=G;i>=1;i--){
int y=b[i].count();ans+=x[i]*x[i];
for (int j=lnk[i][1];j;j=nxt[j][1]){
ans+=(y-(b[i]&b[son[j][1]]).count())*x[son[j][1]];
b[son[j][1]]=b[son[j][1]]|b[i];
}
}
printf("%d\n",ans);
return 0;
}