2020hdu多校第六场1006A Very Easy Graph Problem

题目地址http://acm.hdu.edu.cn/showproblem.php?pid=6832

题意:在一个n个结点,m条边的无向连通图中,且第i条边的权值为2i,每个结点有一个值,为1或者0。d(i,j)表示结点i到结点j之间的最短距离。对所有节点求所有的可能配对形式d(i,j)*[a[i]==1^a[0]==0]的和,最后对1e9+7求余。

输入:第一行t,表示样例个数。每个样例第一行两个整数n,m。接下来一行n个整数,表示结点的权值,接下来m行,每行u,v表示u,v之间存在一条边

输出:每个样例输出一个整数

题解:在这里我们需要注意这个权值是比较特殊的,也就是如果第i条边(权值为2i)是连接结点j和结点k,但是在这之前j和k已经相连了,那么这条边(权值2i)永远都不会被最短路经过,因为前i-1条边的总权值是2i-2,小于2i.所以我们便可以将这个无向连通图转换为一棵最小生成树(既是最小生成树也是任意两个结点之间的距离都是最小)。这里的n,m都是1e5的数量级别,所以如果暴力求两个结点的最短路那么肯定会超时。所以我们可以转换一下思路,求每条边的贡献值,也就是如果这条边最后会出现在最短路中,我们需要计算的是在最短路中出现了多少次,可以求出这条边两边各自的1和0的数量,那么这条边在最短路中的次数就是left_0*right_1+left_1*right_0,那么这条边的在最后的结果中的贡献就是(left_0*right_1+left_1*right_0)*这条边的权值。那么现在主要的就是求出每条边两边的1和0的数量,可以使用dfs进行求解。

AC代码

#include<iostream>
#include<vector>
using namespace std;
#define ll long long  int
const ll N=2e5+5;
const ll mod=1e9+7;
vector<pair<int,ll> >E[N];
int fa[N],a[N],sum0,sum1,k;
struct edge{
    int p0,p1;
    ll w;
}b[N];
struct edge operator+(struct edge b1,struct edge b2){//重载运算符 
    struct edge b3;
    b3.p0=b1.p0+b2.p0;
    b3.p1=b1.p1+b2.p1;
    b3.w=0;
    return b3;
}
int find(int x){
    if(x==fa[x]) return x;
    else return fa[x]=find(fa[x]);
}
void merge(int x,int y){
    int rx=find(x),ry=find(y);
    fa[rx]=ry;
}
struct edge dfs(int now,int pre){
    int size=E[now].size();
    pair<int,ll>pa;
    int tem=k;//使用数组存储遍历后边的信息 
    k++;
    ll w=0;
    for(int i=0;i<size;i++){
        pa=E[now][i];
        if(pre==pa.first){//当与上一次遍历的节点相同时,先存储这条边的权重 
            w=pa.second;
            continue;
        }
        b[tem]=b[tem]+dfs(pa.first,now);
    }
    b[tem].w=w;//存储的是now与pre两个结点之间的边的信息 
    if(a[now]==0) b[tem].p0++;
    else b[tem].p1++;
    return b[tem];
}
int main(){
    int t;cin>>t;
    int n,m;
    while(t--){
        cin>>n>>m;sum0=0,sum1=0;a[0]=0;k=0;
        for(int i=1;i<=n;i++) E[i].clear(); //这里一定要有,之前没有,一直runtime error 
        for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化。并查集用于求解最小生成树 
        for(int i=0;i<=n;i++) b[i].p0=0,b[i].p1=0,b[i].w=0; //初始化 
        for(int i=1;i<=n;i++){ //输入结点权值 
            cin>>a[i];
            if(a[i]==1) sum1++; //记录所有的1和0的数量。到时就可以只计算边的一边的1和0的数量就可以了 
            else sum0++;
        }
        int u,v;
        ll base=1;//边的权值 
        for(int i=1;i<=m;i++){
            cin>>u>>v;
            base*=2;
            base%=mod;
            if(find(u)==find(v)) continue;
            merge(u,v);
            E[v].push_back(make_pair(u,base));//使用邻接表存储树的信息 
            E[u].push_back(make_pair(v,base));
        }
        dfs(1,-1);//dfs求解树中每条边的贡献次数 
        ll sum=0;
        for(int i=1;i<n;i++){//求解最后的结果 
            sum=sum+(sum0-b[i].p0)*b[i].p1*b[i].w;
            sum%=mod;
            sum=sum+(sum1-b[i].p1)*b[i].p0*b[i].w;
            sum%=mod;
        }
        cout<<sum<<endl;
    }
    return 0;
}

写于2020/8/7 12:16

 

posted @ 2020-08-07 12:16  白菜茄子  阅读(212)  评论(0编辑  收藏  举报