【题目】
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 n≤150,m≤3×105,Q≤5×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(ω(mlogm+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;
}