2020 Multi-University Training Contest 6 1006 A Very Easy Graph Problem (最小生成树 + 树 dp)

链接:A Very Easy Graph Problem
题意:
给一个 n 个点 m条边的联通图(分黑白两种点),每条边的权值 为 2 ^ k, k 为依次给出边的序号,求所有所有黑点到白点的距离和的最小值。
思路:

  1. 一开始看上去还以为是最短路,但连边权都存不下来,又有这么多点要跑,肯定就不对了,然后就不会咋写了。
    2.那把问题转化一下, 他要求所有黑点到白点的最小距离,我们可以求一颗生成树,算每条边的贡献时 只要计算出这条边两侧黑点数和白点数分别是多少就好了(就可以算出这条边走了几次)。
  2. 那怎么才能是最小呢,又因为前面任意几条边的和都会小于后一条边,所以选编号小的边一定是最优的,所以按编号从小到大构建生成树就好了。类似题目 那里是每条边的权值是斐波那契数列。推荐学长博客 类似题
#include<iostream>
#include<cstdio>
#include<stack>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll ;
const int maxn=1e6+7;
const int mod=1e9+7;
int head[maxn],vis[maxn],num,fa[maxn],t,s1,s2,n,m;
ll dp[maxn][3];
ll ans;
struct node{
    int v,next,w;
}e[maxn];
ll poww(ll a,ll b){
       ll ans=1;
       while(b>0){
            if(b&1) ans=ans*a%mod;
            a=a*a%mod;
            b>>=1;
       }
      return ans;
}
void add(int u,int v,int w){
     e[num].v = v;
     e[num].w = w;
     e[num].next = head[u];
     head[u] = num ++;
}
void dfs1(int u,int pre){
     if(vis[u] == 1) dp[u][1] = 1;
     if(vis[u] == 0) dp[u][0] = 1;
     for(int i = head[u]; i != -1; i = e[i].next){
         int v = e[i].v;
         if(v == pre) continue;
         dfs1(v,u);
         dp[u][0] += dp[v][0];
         dp[u][1] += dp[v][1];
     }
}
void dfs2(int u,int pre){
     for(int i = head[u]; i != -1; i = e[i].next){
         int v = e[i].v;
         int w = e[i].w;
         if(v == pre) continue;
         dfs2(v,u);
         ans = (ans + dp[v][0] * (s1 - dp[v][1]) % mod * poww(2,w)) % mod;
         ans = (ans + dp[v][1] * (s2 - dp[v][0]) % mod * poww(2,w)) % mod;
     }
}
int find(int x){
     if(fa[x] != x) fa[x] = find(fa[x]);
     return fa[x];
}
void conbine(int u,int v){
     int temp1 = find(u);
     int temp2 = find(v);
     fa[temp2] = temp1;
}
void init(){
      for(int i = 1; i <= n; i++){
          fa[i] = i;head[i] = -1;
      }
      memset(dp,0,sizeof(dp));
      s1 = 0;s2 = 0;
      ans = 0;
}
int main(){
    cin>>t;
    while(t--){
         scanf("%d%d",&n,&m);
         init();
         for(int i = 1; i <= n; i++){
             scanf("%d",&vis[i]);
             if(vis[i] == 1) s1 ++;
             if(vis[i] == 0) s2 ++;
         }
         for(int i = 1,u,v;i <= m; i++){
             scanf("%d%d",&u,&v);
             if(find(u) == find(v)) continue;
             conbine(u,v);
             add(u,v,i);
             add(v,u,i);
         }
         dfs1(1,-1);
         dfs2(1,-1);
         printf ("%lld\n",ans);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值