HDU 5765 Bonds(bfs+高维前缀和)

256 篇文章 0 订阅
125 篇文章 0 订阅

Description
给一个n个点的无向连通图,求每条边被多少个极小割边集包括
Input
第一行一整数T表示用例组数,每组用例首先输入两个整数n和m表示点数和边数,之后m行每行两个整数u和v表示u,v之间有一条边
(T<=20,2<=n<=20,n-1<=m<=n*(n-1)/2)
Output
对于每组用例,输出m个整数表示每条边被多少个极小割边集包括
Sample Input
2
3 3
0 1
0 2
1 2
3 2
0 1
0 2
Sample Output
Case #1: 2 2 2
Case #2: 1 1
Solution
极小割边集会把整张图分成两个连通块,因为至多20个点,所以可以用一个20位的二进制数表示一个点集,首先找出所有连通点集,由于每个连通点集必然可以拿出一个点与剩余点有边且剩余点连通,故可以从一个点的点集开始转移求出所有连通点集,具体操作看代码,关键是求出任一点集的邻接点,然后从一个连通点集开始,往其中加入这个点集的某个邻接点以及和这个邻接点可达的点就可以构成一个新的连通点集,这个通过一个bfs可以实现,然后我们就得到了所有点集的连通状态,对于某个点集i,如果i是连通的而且i的补集j是连通的,那么说明i和j之间连的所有边构成一个极小割边集,对这些割边集一一更新答案显然不行,我们可以采用对总体累加,对不合法情况累加,然后让两者相减得到答案,换句话说,对于边u->v,其答案=u和v处于两个不同且互补的连通点集的方法数=(两个连通点集互补的方法数tot-u和v属于同一个连通点集中的方法数)/2,右边第一项可以枚举所有点集i,如果i和它的补集j都是连通的则tot++,sum[i]++,sum[j]++,sum[i]++说明i这个点集中任意两点处于同一个连通点集的方法数加一,这样做一遍后我们得到了右边第一项tot,但是u和v处于同一个连通点集的方法数还没有得到,考虑到所有包含u和v的点集都是点集{u,v}的超集,因此可以对sum数组用一遍高维前缀和,那么sum[(1<< u)|(1<< v)]即为右边第二项
Code

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 1111111
int T,n,m,u[222],v[222],reach[maxn],q[maxn],mask[maxn],sum[maxn],tot,Case=1;
int lowbit(int x)
{
    return x&(-x);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        tot=0;
        memset(reach,0,sizeof(reach));
        memset(mask,0,sizeof(mask));
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u[i],&v[i]);
            reach[1<<u[i]]|=1<<v[i];
            reach[1<<v[i]]|=1<<u[i];
        }
        //reach[i]表示与i这个点集邻接的点 
        for(int i=1;i<1<<n;i++)
            reach[i]|=reach[i-lowbit(i)]|reach[lowbit(i)];
        //bfs找所有连通块 
        int l=0,r=0;
        for(int i=0;i<n;i++)q[r++]=1<<i,mask[1<<i]=1;
        while(l<r)
        {
            int now=q[l++];
            int around=reach[now]^now;//从一个连通块的邻接点开始扩展找新的连通块 
            while(around)
            {
                int temp=lowbit(around)|now;
                if(!mask[temp])mask[temp]=1,q[r++]=temp;
                around-=lowbit(around);
            }
        }
        for(int i=0;i<1<<n;i++)
        {
            int j=((1<<n)-1)^i;
            if(mask[i]&&mask[j])sum[i]++,sum[j]++,tot++;
        }
        //高维前缀和 
        for(int j=0;j<n;j++)
            for(int i=(1<<n)-1;i>=0;i--)
                if(!(i&(1<<j)))sum[i]+=sum[i|(1<<j)];
        printf("Case #%d:",Case++);
        //u->v这条边作为割边的次数(总数-包含u和v的连通块数)/2 
        for(int i=1;i<=m;i++)
            printf(" %d",(tot-sum[(1<<u[i])|(1<<v[i])])/2);
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值