【kosaraju算法+bitset+回滚莫队/st表】[Lydsy2017省队十连测]友好城市

【题目】
BZOJ
给定一幅 n n n个点 m m m条边的有向图 Q Q Q次询问若只使用 [ l , r ] [l,r] [l,r]这些边,有多少个点对能互达。
n ≤ 150 , m ≤ 3 × 1 0 5 , Q ≤ 5 × 1 0 4 n\leq 150,m\leq 3\times 10^5,Q\leq 5\times 10^4 n150,m3×105,Q5×104

【解题思路】
我们知道一次 tarjan \text{tarjan} tarjan O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)的,显然会爆炸。

有一种叫做 kosaraju \text{kosaraju} kosaraju的算法,如果知道了邻接矩阵,可以在 O ( n 2 ω ) O(\frac {n^2} {\omega}) O(ωn2)的时间内得到所有强连通分量,其中 ω \omega ω bitset \text{bitset} bitset压的位数。

这个算法具体是怎么实现的呢?首先任选起点对整幅图进行 DFS \text{DFS} DFS,记录一个点第一次经过的时间(初始时间,事实上并不用记)和它所有可达节点访问完后的时间(结束时间)。然后在图的反图(全部边反过来)上,按照结束时间从大到小进行 DFS \text{DFS} DFS,每次 DFS \text{DFS} DFS所经过的所有节点就是一个强连通分量。

那么如果用邻接链表来存的话复杂度也是 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)的,但在稠密图上,它的瓶颈在与寻找与一个点相连且为访问过的点,于是这就可以用 bitset \text{bitset} bitset来压位了。这里用到了 . _ F i n d _ f i r s t ( ) .\_Find\_first() ._Find_first()这个函数,可以找到第一个 1 1 1的位置。

接下来的问题就是要得到一个邻接矩阵了,不妨考虑分块,那么每次询问我们可以在 O ( n 2 m ω ) O(\frac {n^2\sqrt m} {\omega}) O(ωn2m )的时间内得到邻接矩阵,但这样并不优秀。那么我们不妨将询问离线,用莫队来解决这个问题,由于这个问题插入很简单但删除困难,我们可以用回滚莫队,即保留整块的信息,散块暴力加,这样可以在 O ( ( Q + m ) m ) O((Q+m)\sqrt m) O((Q+m)m )的时间内得到所有询问的邻接矩阵。

于是总的复杂度就是 O ( ( Q + m ) m + Q n 2 ω ) O((Q+m)\sqrt m+\frac {Qn^2} {\omega}) O((Q+m)m +ωQn2)

如果分块以后用 ST \text{ST} ST表来维护矩阵,那么可以做到 O ( ( m log ⁡ m + Q ) n 2 ω + Q m ) O(\frac {(\sqrt m \log m+Q)n^2} {\omega} +Q\sqrt m) O(ω(m logm+Q)n2+Qm ),似乎差不多。

【参考代码】

#include
using namespace std;

const int N=155,M=3e5+10,lim=555;
typedef bitset<N> bs;

namespace IO
{
	inline int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	inline void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	inline void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;

namespace DreamLolita
{
	int n,m,Q,cnt,ind;
	int a[M],b[M],ans[M],st[M],ed[M],out[N];
	bs mp[N],rmp[N],t1[N],t2[N],vis;
	struct Tquery
	{
		int l,r,id;
		Tquery(int _l=0,int _r=0,int _id=0):l(_l),r(_r),id(_id){}
		inline friend bool operator < (const Tquery&a,const Tquery&b){return a.r<b.r;}
	};
	vector<Tquery>q[1005][1005];
	inline void dfs(int x)
	{
		bs now;vis[x]=1;
		for(now=mp[x]^(mp[x]&vis);now.any();now=mp[x]^(mp[x]&vis))
			dfs(now._Find_first());
		out[++ind]=x;
	}
	inline int dfsrev(int x)
	{
		int res=1;bs now;vis[x]=1;
		for(now=rmp[x]^(rmp[x]&vis);now.any();now=rmp[x]^(rmp[x]&vis))
			res+=dfsrev(now._Find_first());
		return res;
	}
	inline int kosaraju()
	{
		vis.reset();ind=0;
		for(int i=1;i<=n;++i) if(!vis[i]) dfs(i);
		vis.reset();int res=0;
		for(int i=n,x;i;--i) if(!vis[out[i]])
			x=dfsrev(out[i]),res+=x*(x-1)/2;
		return res;
	}
	inline void clear(){for(int i=1;i<=n;++i) mp[i].reset(),rmp[i].reset();}
	inline void ins(int x,int y){mp[x].set(y);rmp[y].set(x);}
	inline void brute(int l,int r,int id){for(int i=l;i<=r;++i)ins(a[i],b[i]);ans[id]=kosaraju();clear();}
	inline void solve()
	{
		n=read();m=read();Q=read();
		for(int i=1;i<=m;++i) a[i]=read(),b[i]=read();
		for(int i=1,j;i<=m;i=j+1) j=min(i+lim-1,m),st[++cnt]=i,ed[cnt]=j;
		for(int i=1;i<=Q;++i)
		{
			int l=read(),r=read();
			if(r-l+1<=2*lim) brute(l,r,i);
			else q[(l-1)/lim+2][r/lim].push_back(Tquery(l,r,i));
		}
		for(int i=1;i<=cnt;i++) for(int j=i;j<=cnt;j++) 
			sort(q[i][j].begin(),q[i][j].end());
		for(int i=1;i<=cnt;++i)
		{
			int now=ed[i-1];
			for(int j=i;j<=cnt;++j)
			{
				while(now<ed[j]) ++now,ins(a[now],b[now]);
				for(int k=0;k<(int)q[i][j].size();++k)
				{
					Tquery x=q[i][j][k];
					while(now<x.r) ++now,ins(a[now],b[now]);
					for(int l=1;l<=n;++l) t1[l]=mp[l],t2[l]=rmp[l];
					for(int l=st[i]-1;l>=x.l;--l) ins(a[l],b[l]);
					ans[x.id]=kosaraju();
					for(int l=1;l<=n;++l) mp[l]=t1[l],rmp[l]=t2[l];
				}
			}
			clear();
		}
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ5218.in","r",stdin);
	freopen("BZOJ5218.out","w",stdout);
#endif
	DreamLolita::solve();
	return 0;
}

你可能感兴趣的:(其他-bitset,分而治之-分块,图论-kosaraju)