【JZOJ5064】【GDOI2017第二轮模拟day2】友好城市 Kosarajo算法+bitset+ST表+分块

题面

在Byteland 一共有n 座城市,编号依次为1 到n,这些城市之间通过m 条单向公路连接。
对于两座不同的城市a 和b,如果a 能通过这些单向道路直接或间接到达b,且b 也能如此到达a,那么它们就会被认为是一对友好城市。
Byteland 的交通系统十分特殊,第i 天只有编号在[li, ri] 的单向公路允许通行,请写一个程序,计算每天友好城市的对数。
注意:(a, b) 与(b, a) 没有区别。

70

Kosarajo算法

这是一个区别于tarjan算法的求强连通分量的算法。

流程

1.在逆图上进行一次dfs,然后记录下每个点的后序编号(?)。
e.g.

void dfs(int v){
	dfs(next(v));  //先往后继递归
	st[++st[0]]=v; //再在这记录后序编号
}

2.按后序编号从大到小在原图上再进行一次dfs,所能走到的就是与这个点处于同一强连通分量的点。
3.时间复杂度为\(O(n+m)\)

正文

我们看到给出的区间的左端点和右端点都是不减的,就有边只会进出一次。
所以我们可以用邻接矩阵维护边,然后就可以使用Kosarajo算法统计答案。
这样的时间复杂度为\(O(n^2*q)\),然而这还是过不了70分。

bitset优化

由于边不存在权值,所以我们用bitset来存储邻接矩阵。
然后Kosarajo算法统计答案时,也要用到bitset的位运算优化。
于是就能把复杂度优化到\(O(\frac{1}{32}n^2*q)\)

可能会用到的bitset函数

.reset(),归零;
._Find_next(int v),查找第v为的第一个1,返回位置。
Warning:bitset的下标是从0开始算,所以如果要从头开始找,就用._Find_next(-1)。

100

100分与70分的区别就是,边可能会重复加入。
注意到,如果对于两个已有的边集(邻接矩阵),那么我们可以利用bitset来优化合并,达到\(O(\frac{n^2}{32})\)的复杂度,是很优秀的。
我们给\(m\)条边分块,共\(\sqrt m\)块,每个块考虑使用bitset来存储邻接矩阵;
按照一般的分块套路,我们确实可以用\(O(q*(\sqrt m*\frac{n^2}{32}+\sqrt m))\)来维护邻接矩阵。
但是仍然无法被题目苛刻的条件所接受。
于是我们考虑对块建立ST表,那么就能把维护的时间降到\(O(q*(log_{\sqrt m}*\frac{n^2}{32}+\sqrt m))\)
就能通过本题。

为什么不用tarjan,而用Kosarajo代替

对于tarjan而言,他需要遍历一些已经被访问的点,所以不能使用bitset优化;
而Kosarajo,每个点只会被遍历一次,所以能使用bitset优化。

为什么不用线段树,而用ST表和分块代替

线段树的空间不能被接受,是\(O(m*log_m*\frac{n^2}{32})\)

Code

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bitset>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;i++)
#define fd(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
const char* fin="friend.in";
const char* fout="friend.out";
const int inf=0x7fffffff;
const int maxn=157,maxm=300007,maxk=557,maxl=10;
int n,m,q,a[maxm][2],ks,num,st[maxn],cnt=0,pre[maxn],ans;
bitset<maxn> b[maxk][maxl][maxn],bb[maxk][maxl][maxn],bz,p[maxn],pp[maxn];
void make(int l,int r){
	int k=maxl-1;
	fo(i,1,n) p[i].reset(),pp[i].reset();
	while (l<=r){
		if (l+(1<<k)-1<=r){
			fo(i,1,n) p[i]|=b[l][k][i],pp[i]|=bb[l][k][i];
			l+=(1<<k);
		}
		if (k>0) k--;
	}
}
void dfs(int v){
	bz.reset(v);
	while (1){
		int k=(pp[v]&bz)._Find_next(0);
		if (k>n) break;
		dfs(k);
	}
	st[++st[0]]=v;
}
void Dfs(int v){
	cnt++;
	bz.reset(v);
	while (1){
		int k=(p[v]&bz)._Find_next(0);
		if (k>n) break;
		Dfs(k);
	}
}
void kosarajo(){
	ans=0;
	bz.set();
	st[0]=0;
	fo(i,1,n)
		if (bz[i]){
			dfs(i);
		}
	bz.set();
	fd(i,st[0],1){
		if (bz[st[i]]){
			cnt=0;
			Dfs(st[i]);
			ans+=cnt*(cnt-1)/2;
		}
	}
}
int main(){
	freopen(fin,"r",stdin);
	freopen(fout,"w",stdout);
	scanf("%d%d%d",&n,&m,&q);
	fo(i,1,m) scanf("%d%d",&a[i][0],&a[i][1]);
	ks=int(sqrt(m));
	int j=1,k=ks;
	fo(i,1,m){
		if (i>k){
			k+=ks;
			j++;
		}
		b[j][0][a[i][0]].set(a[i][1]);
		bb[j][0][a[i][1]].set(a[i][0]);
	}
	num=j;
	fd(i,num,1){
		fo(j,1,maxl-1){
			if (i+(1<<(j-1))>num) break;
			fo(k,1,n){
				b[i][j][k]=b[i][j-1][k]|b[i+(1<<(j-1))][j-1][k];
				bb[i][j][k]=bb[i][j-1][k]|bb[i+(1<<(j-1))][j-1][k];
			}
		}
	}
	fo(i,1,q){
		int l,r;
		scanf("%d%d",&l,&r);
		int tmp=(l-1)/ks+1,tmd=(r-1)/ks+1;
		make(tmp+1,tmd-1);
		if (tmp!=tmd){
			fo(j,l,tmp*ks) p[a[j][0]].set(a[j][1]),pp[a[j][1]].set(a[j][0]);
			fo(j,(tmd-1)*ks+1,r) p[a[j][0]].set(a[j][1]),pp[a[j][1]].set(a[j][0]);
		}else fo(j,l,r) p[a[j][0]].set(a[j][1]),pp[a[j][1]].set(a[j][0]);
		/*fo(i,1,n) cout<<p[i]<<endl;
		fo(i,1,n) cout<<pp[i]<<endl;*/
		kosarajo();
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2017-04-18 11:56  hiweibolu  阅读(460)  评论(0编辑  收藏  举报