题意:有n个顶点和m条边的无向图,顶点有1和0两种,第i条边的权值为2的i次方。求两点且两点一点为1,另一点为0之间的最小距离的和。
思路:由于第i条边的权值为2的i次方,又因为
所以只要能在i-1的边到达,就不需要走第i条。可以利用最小生成树来解决。
注意:最小生成树的两种算法克鲁斯卡尔算法(Kruskal)和普里姆算法(Prim);dfs算法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e5+10;
int n,m;ll ans=0;
int zero=0,one=0;
vector<pair<int,ll>>G[maxn];
int dp[maxn][2],col[maxn],f[maxn];//col用于存储点类型,f为parent列表,初始为-1
void dfs(int u,int f){
dp[u][0]=dp[u][1]=0;
dp[u][col[u]]++;
for(auto it:G[u]){
int v=it.first;
if(v==f)continue;
dfs(v,u);
dp[u][0]+=dp[v][0];
dp[u][1]+=dp[v][1];
}
for(auto it:G[u]){
int v = it.first;
if(v==f)continue;
ans=(ans+1ll*dp[v][0]*(one-dp[v][1])%mod*it.second)%mod;
ans=(ans+1ll*dp[v][1]*(zero-dp[v][0])%mod*it.second)%mod;
}
}
int find(int x){
return f[x]==x?x:(f[x]=find(f[x]));
}//找父节点
int main(){
//freopen("./data/1.in","r",stdin);
int T;cin>>T;
while(T--){
ans=0,zero=one=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)dp[i][0]=dp[i][1]=0,G[i].clear();
for(int i=1;i<=n;i++)scanf("%d",&col[i]),f[i]=i;
for(int i=1;i<=n;i++){//统计1和0的个数
if(col[i])one++;
else zero++;
}
ll val=1;
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
val=val*2%mod;//输入边
int fu=find(u),fv=find(v);
if(fu==fv)continue;
f[fu]=fv;
G[u].push_back(make_pair(v,val));
G[v].push_back(make_pair(u,val));
}
dfs(1,-1);
printf("%lld\n",ans);
}
}