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

本文介绍了一种基于Kosarajo算法求解强连通分量的方法,并使用bitset进行优化,解决了边集动态变化的问题。文章详细阐述了算法步骤及优化技巧,最终实现高效计算每天友好城市的对数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

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

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;
}

转载于:https://www.cnblogs.com/hiweibolu/p/6727057.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值