【bzoj2208】连通数 tarjan缩点&状压常数优化

       这道题目还想并没有比O(N^3)更快的算法了。。除非从树的分治入手?

       首先tarjan缩点(显然),然后就是个DAG。然后给缩成的强连通分量一个权值为它所含的点的个数,然后对于一个强连通分量该处的答案就是为该强连通分量的权值乘上这个强连通分量所能到达的点的个数(包括自身)。假设用f[i]表示强连通分量i能到达的点的集合,显然f[i]|={f[j]}当且仅当存在边(u,v)使u在强连通分量i中且v在j中,这个用记忆化轻松解决(递推也行)。

       如果用二进制来表示,显然会爆(2^n),所以将这个二进制数转化为n/30个二进制数,这样每个数的大小就只有2^30了。由于求f数组时间复杂度为O((N+M)N/30)=O(N^3),但考虑到n最大为2000,而且还有1/30这样一个很小的常数,实际上运行速度还是很快的。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2005
using namespace std;

int n,m,cnt,tp,tmp,dfsclk,s[N],pos[N],low[N],scc[N],sum[N],f[N][105];
int tot,fst[N],pnt[N*N],nxt[N*N]; bool vis[N],a[N][N];
void dfs(int x){
	s[++tp]=x; pos[x]=low[x]=++dfsclk; int i;
	for (i=1; i<=n; i++) if (a[x][i]){
		if (!pos[i]){ dfs(i); low[x]=min(low[x],low[i]); } else
		if (!scc[i]) low[x]=min(low[x],pos[i]);
	}
	if (low[x]==pos[x]){		
		for (sum[++cnt]=1; s[tp]!=x; tp--){
			scc[s[tp]]=cnt; sum[cnt]++;
		}
		scc[x]=cnt; tp--;
	}
}
void add(int aa,int bb){
	pnt[++tot]=bb; nxt[tot]=fst[aa]; fst[aa]=tot;
}
void solve(int x){
	if (vis[x]) return; vis[x]=1; int p,i;
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p]; solve(y);
		for (i=0; i<=m; i++) f[x][i]|=f[y][i];
	}
}
int work(int x){
	int ans=0; for (; x; x-=x&(-x)) ans++; return ans;
}
int main(){
	scanf("%d",&n); int i,j;
	for (i=1; i<=n; i++){
		char ch=getchar(); while (ch<'0' || ch>'1') ch=getchar();
		for (j=1; j<=n; j++){
			a[i][j]=(ch=='1'); ch=getchar();
		}
	}
	for (i=1; i<=n; i++) if (!pos[i]) dfs(i);
	int ans=0; m=n/30;
	for (i=1; i<=n; i++) f[scc[i]][i/30]|=1<<(i%30);
	for (i=1; i<=n; i++)
		for (j=1; j<=n; j++) if (a[i][j] && scc[i]!=scc[j]) add(scc[i],scc[j]);
	for (i=1; i<=cnt; i++) if (!vis[i]){
		solve(i); for (j=0; j<=m; j++) ans+=sum[i]*work(f[i][j]);	
	}
	printf("%d\n",ans);
	return 0;
}

by lych

2016.1.27

你可能感兴趣的:(Tarjan,强连通分量,状态压缩,记忆化搜索,缩点)