HDU 6832 A Very Easy Graph Problem

A Very Easy Graph Problem

题意
给了一张连通图,有黑白两种颜色的点,求所有黑白点对之间的距离之和。
对于给的边的顺序,第i条边的权值为2^i

思路
不难知道2 ^ 1 + 2 ^ 2 +2 ^ 3 + … + 2 ^ (i-1) < 2 ^ i
所以最后的图一定是原图的最小生成树,用并查集保存下来选择的边即可。
然后我们计算在最小生成树中的边对答案的贡献。
对于边(u,v),我们只需要知道从1到u有多少个黑点,从v到n有多少个白点,贡献就是左边黑点个数乘右边白点个数乘上边的权值,那么同理要处理左边白点,右边黑点的贡献即可。
那么黑白点的个数怎么求呢?我们只需要对这棵树进行一次dfs
cnt[x][0]表示以x为结点的子树的白点个数有多少
cnt[x][1]表示以x为结点的子树的黑点个数有多少

最后对生成树中的边,进行计算即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=3e5+5;
ll q[N],f[N];
int find(int x){
    return f[x]==x?x:f[x]=find(f[x]);
}
struct node{
    int x,y;
}edge[N];
struct Edge{
    int to,next;
}e[N];
int h[N],tot;
void add(int x,int y){
    e[tot]={y,h[x]},h[x]=tot++;
}
bool vis[N],id[N];
ll cnt[N][2];
int dfn[N],num;
void dfs(int x,int f){
    cnt[x][1]=id[x]^0;
    cnt[x][0]=id[x]^1;
    dfn[x]=++num;
    for(int i=h[x];~i;i=e[i].next){
        if(e[i].to==f) continue;
        dfs(e[i].to,x);
        cnt[x][1]+=cnt[e[i].to][1];
        cnt[x][0]+=cnt[e[i].to][0];
    }
}
int main(){
    q[0]=1;
    for(int i=1;i<N;i++) q[i]=q[i-1]*2%mod;
    ios::sync_with_stdio(0);
    int T;cin>>T;
    while(T--){
        int n,m;cin>>n>>m;
        tot=0,num=0;
        for(int i=1;i<=n;i++) f[i]=i,h[i]=-1;///并查集初始化 前向星初始化
        for(int i=1;i<=m;i++) vis[i]=0;///记录边是否在生成树中
        for(int i=1;i<=n;i++)  cin>>id[i];///记录点的黑白颜色
        
        for(int i=1;i<=m;i++){
            int x,y;cin>>x>>y;
            edge[i]={x,y};
            int fx=find(x),fy=find(y);
            if(fx==fy) continue;
            f[fx]=fy;
            vis[i]=1;
            add(x,y),add(y,x);
        }
        dfs(1,0);
        ll ans=0;
        for(int i=1;i<=m;i++){
            if(!vis[i]) continue;
            int fa=edge[i].x,son=edge[i].y;
            if(dfn[fa]>dfn[son]) swap(fa,son);///注意父子关系 因为是无向边
            ans+=cnt[son][1]*(cnt[1][0]-cnt[son][0])%mod*q[i]%mod;
            ans%=mod;
            ans+=cnt[son][0]*(cnt[1][1]-cnt[son][1])%mod*q[i]%mod;
            ans%=mod;
        }
        cout<<ans<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我不会c语言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值