[tarjan+bitset]BZOJ 2208——[Jsoi2010]连通数

2208: [Jsoi2010]连通数

题目描述

这里写图片描述

解题报告

这题的题目描述非常清晰,看到网上有dalao用 bitset 优化 Floyd 就过了。博主用 tarjan 缩点然后用 bitset 优化传递闭包就可以。

但是看到很多人缩点完刷拓扑啊,其实这个操作很鸡肋。学过 2sat 应该有所了解,拓扑序就是 tarjan 刷出来的 scc 序(画个图就知道了)。

然后最后刷答案的时候注意一下就可以了,我开了两个数组,一个是或起来之后当前节点的状态,一个是原来的状态,这样的操作使调用 bitset 的次数变多,所以效率很低( 3000ms+ )。
希望各位dalao能告诉我更优的方法。

#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int maxn=2005,maxm=4000005;
int tot[2],lnk[maxn][2],son[maxm][2],nxt[maxm][2],x[maxn];
int n,top,s[maxn],G,scc[maxn],low[maxn],dfn[maxn],tim,ans;
bool ins[maxn],vis[maxn][maxn];
bitset<maxn> a[maxn],b[maxn];
void add(int x,int y,int id){
    nxt[++tot[id]][id]=lnk[x][id];lnk[x][id]=tot[id];son[tot[id]][id]=y;
}
inline char _read(){
    char ch=getchar();
    while (ch!='0'&&ch!='1') ch=getchar();
    return ch;
}
void tarjan(int x){
    dfn[x]=++tim;low[x]=tim;
    s[++top]=x;ins[x]=1;
    for (int j=lnk[x][0];j;j=nxt[j][0])
    if (!dfn[son[j][0]]){tarjan(son[j][0]);low[x]=min(low[x],low[son[j][0]]);}
    else if (ins[son[j][0]]) low[x]=min(low[x],dfn[son[j][0]]);
    if (dfn[x]==low[x]){
        G++;
        while (s[top]!=x) scc[s[top]]=G,ins[s[top]]=0,a[G][s[top--]]=1;
        scc[x]=G;ins[x]=0;a[G][x]=1;top--;
    }
}
void maker(){
    for (int i=1;i<=n;i++)
    for (int j=lnk[i][0];j;j=nxt[j][0])
    if (scc[i]>scc[son[j][0]]&&!vis[scc[i]][scc[son[j][0]]])
    add(scc[i],scc[son[j][0]],1),vis[scc[i]][scc[son[j][0]]];
}
int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
    if (_read()=='1') add(i,j,0);
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
    for (int i=1;i<=G;i++) x[i]=a[i].count();
    maker();
    for (int i=1;i<=G;i++) b[i]=a[i];
    for (int i=G;i>=1;i--){
        int y=b[i].count();ans+=x[i]*x[i];
        for (int j=lnk[i][1];j;j=nxt[j][1]){
            ans+=(y-(b[i]&b[son[j][1]]).count())*x[son[j][1]];
            b[son[j][1]]=b[son[j][1]]|b[i];
        }
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值