链接:A Very Easy Graph Problem
题意:
给一个 n 个点 m条边的联通图(分黑白两种点),每条边的权值 为 2 ^ k, k 为依次给出边的序号,求所有所有黑点到白点的距离和的最小值。
思路:
- 一开始看上去还以为是最短路,但连边权都存不下来,又有这么多点要跑,肯定就不对了,然后就不会咋写了。
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);
}
}