#include<iostream>#include<cmath>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<map>#include<set>usingnamespace std;#define INF 0x3f3f3f#define ll long longconst ll mod =1000000007;constint N =1e5+10;int n, m, zero, one, u, v;
ll ans;
ll dp[N][2];int s[N], f[N];
vector<pair<int,ll>>G[N];intffind(int x){return f[x]==x?x:(f[x]=ffind(f[x]));}voidinit(){// 初始化
ans = zero = one =0;for(int i =0; i <= n; i++){
dp[i][0]= dp[i][1]=0;// dp数组存储每个节点子树的1以及0的个数
f[i]= i;// 并查集数组
G[i].clear();// 清空图}}voiddfs(int u,int f){
dp[u][s[u]]++;// 自己节点的0 1个数++for(int i =0; i <(int)G[u].size(); i++){int v = G[u][i].first;if(v==f)continue;// 非父节点dfs(v,u);// 继续搜索子树
dp[u][0]+= dp[v][0];// 加上子树的0的个数
dp[u][1]+= dp[v][1];// 加上子树的1的个数}for(int i =0; i <(int)G[u].size(); i++){int v = G[u][i].first;if(v==f)continue;// 贡献值计算// dp[v][0] dp[v][1] 该边的子树的0 1个数// one-dp[v][1] zero-dp[v][0] 总个数减去子树的0 1个数就是其他节点的0 1个数// 最后乘上边权值即为贡献值
ans =(ans+dp[v][0]*(one-dp[v][1])%mod*G[u][i].second)%mod;
ans =(ans+dp[v][1]*(zero-dp[v][0])%mod*G[u][i].second)%mod;}}intmain(){int t;scanf("%d",&t);while(t--){scanf("%d %d",&n,&m);init();for(int i =1; i <= n; i++)scanf("%d",&s[i]);// 统计所有的1以及0的个数for(int i =1; i <= n; i++){if(s[i]) one++;if(!s[i]) zero++;}
ll val =1;for(int i =1; i <= m; i++){scanf("%d %d",&u,&v);
val = val*2%mod;// 边权值// 并查集维护生成树int fu =ffind(u);int fv =ffind(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);}return0;}
2020 Multi-University Training Contest 6 A Very Easy Graph Problem
传送门题目:给定n个点,m条边,每个点的权值为0 or 1,求所有权值为1的点到权值为0的点的最短路的和,第i条边权值的为2^i。思路:首先,根据给定的边权值的条件可以发现:后建的边一定是大于之前建的边的权值和(等比数列求和),所以只要建成一个树即可(并查集维护),这样就处理掉了最短路的问题,建完树之后,我们不妨把节点1当作根节点,然后跑一遍dfs遍历一遍树,统计每个节点的子树的1和0的个数,最后统计每条边要被经过的次数(贡献值),这个次数计算方式,就是当前这条边的子树的0的个数乘上不在其子树上的节点