【2020HDU多校六:图问题】A Very Easy Graph Problem

HDU - 6832 A Very Easy Graph Problem

【难度】

4.5 / 10 4.5/10 4.5/10
中等图论 -> (转化为)树题目

【题意】

给一个无相连通图
n n n个节点, m m m条边
i i i条边的长度为 2 i 2^i 2i
每个节点有权值 a i a_i ai,为0或1

求出
∑ i = 1 n ∑ j = 1 n d ( i , j ) × [ a i = 1 ∧ a j = 0 ] \sum_{i=1}^n\sum_{j=1}^nd(i,j)\times[a_i=1∧a_j=0] i=1nj=1nd(i,j)×[ai=1aj=0]

其中 d ( i , j ) d(i,j) d(i,j)表示节点 i i i j j j之间的最短距离
[ ]是艾佛森括号,括号内表达式为真则值为1,否则为0
∧是逻辑关系并且

【数据范围】

T样例组数   1 ≤ T ≤ 8 \ 1\le T\le 8  1T8
n , m ( 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 ) n,m(1\le n\le 10^5,1\le m\le 2\times 10^5) n,m(1n105,1m2×105)

【样例输入】

T
n,m
a 1 ⋯ a n a_1\cdots a_n a1an
i − t h e d g e , c o n n e c t   t w o   v e r t e x e s i-th\quad edge , connect\ two\ vertexes ithedge,connect two vertexes

1
3 2
0 1 0
3 1
3 2

【样例输出】

10

【解释】

在这里插入图片描述
答案即为 d ( 2 , 1 ) + d ( 2 , 3 ) = 6 + 4 = 10 d(2,1)+d(2,3) = 6+4=10 d(2,1)+d(2,3)=6+4=10

【思路】

在这里插入图片描述
首先来简单地分析一下对于最短路的一些情况。
若如图所示,简单计算即可得知
d i s A − B − C − D − E ( A , E ) = a ( 1 − 2 4 ) 1 − 2 = ( 2 4 − 1 ) a < 2 4 a = d i s A − E ( A , E ) dis_{A-B-C-D-E}(A,E)=\frac{a(1-2^4)}{1-2}=(2^4-1)a<2^4a=dis_{A-E}(A,E) disABCDE(A,E)=12a(124)=(241)a<24a=disAE(A,E)
如果再多枚举其他的点对之间的最小距离,就会发现他们都不会经过 e d g e A − E edge_{A-E} edgeAE
对于这种无用的边,我们可能会想到删掉它。那么把所有的无用边都删除之后会发生什么?
容易想到,这可能是 M S T ( M i n i m u n   S p a n n i n g   T r e e ) MST(Minimun\ Spanning\ Tree) MST(Minimun Spanning Tree)最小生成树算法。
我们用Kruskal算法即可把图转化成不影响答案的树。

再考虑我们已经得到了树了,那么怎么计算每条边的权值(长度)对于答案的贡献

在这里插入图片描述
若我们以A节点作为根节点,我们考虑这条红色的边 E d g e Edge Edge对答案的贡献
贡献易得为 E d g e i 计 算 次 数 × E d g e i 的 权 值 \pmb{Edge_i计算次数\times Edge_i的权值} Edgei×EdgeiEdgei计算次数×Edgei的权值Edgei×Edgei
权值已经得知为 2 i 2^i 2i,那么我们去考虑其边的计算次数

我们考虑这条边的链接的儿子节点(D)及其子树( α 块 部 分 \alpha 块部分 α)
a i = 1 a_i=1 ai=1表示该节点 i i i为黑色节点,反之为白色节点。
我们计算 α 黑 色 节 点 数 量 \alpha_{黑色节点数量} α α 白 色 节 点 数 量 \alpha_{白色节点数量} α
相同的,我们计算除了这个 α \alpha α部分之外的 β \beta β部分,
并计算出 β 黑 色 节 点 数 量 \beta_{黑色节点数量} β β 白 色 节 点 数 量 \beta_{白色节点数量} β

那么,对于每一个黑色节点,我们都应该匹配其他的白色节点。
易得,经过边 E d g e Edge Edge的次数即为
E d g e 计 算 次 数 = α 黑 色 节 点 数 量 × β 白 色 节 点 数 量 + α 白 色 节 点 数 量 × β 黑 色 节 点 数 量 \pmb{Edge_{计算次数}=\alpha_{黑色节点数量}\times \beta_{白色节点数量}+\alpha_{白色节点数量}\times \beta_{黑色节点数量}} Edge=α×β+α×βEdge=α×β+α×βEdge=α×β+α×β

那么怎么去计算呢?

我们可以使用 d f s dfs dfs计算某个结点及其子树所包括的 α 黑 色 节 点 数 量 与 α 白 色 节 点 数 量 \alpha_{黑色节点数量}与\alpha_{白色节点数量} αα

而对于其他的部分直接求比较复杂,我们只需简单求出共有多少黑色结点与白色结点即可。

β 白 色 节 点 数 量 = S u m 总 白 色 结 点 数 量 − α 白 色 节 点 数 量 β 黑 色 节 点 数 量 = S u m 总 黑 色 结 点 数 量 − α 黑 色 节 点 数 量 \beta_{白色节点数量}=Sum_{总白色结点数量}-\alpha_{白色节点数量}\\ \beta_{黑色节点数量}=Sum_{总黑色结点数量}-\alpha_{黑色节点数量}\\ β=Sumαβ=Sumα

【AC核心代码】

时间复杂度 O ( n + m ) O(n+m) O(n+m)
(注意:因为边权取模,不能用sort!)
因为边权单调递增,直接Kruskal即可
并查集使用启发式合并的路径压缩即可。

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 4e5+50;
const ll  MOD = 1e9+7;

int aa[MAX];

struct Node{		///存边的结构体
    int ta,tb;
    ll w;
}edge[MAX];

vector<int>G[MAX];      /// node
queue<int>E;		/// 存对结果有贡献的边

int fa[MAX];		/// 并查集部分
int find_fa(int x){
    if(x==fa[x])return x;
    return fa[x] = find_fa(fa[x]);
}
void add_edge(int x,int y,int bh){
    int fx = find_fa(x);
    int fy = find_fa(y);
    if(fx!=fy){
        fa[fx]=fy;
        G[x].push_back(y);
        G[y].push_back(x);
        E.push(bh);
    }
}

int cnt;
ll sum_white ;
ll sum_black ;
ll down_black[MAX];
ll down_white[MAX];
int fafa[MAX];			/// 存结点的前驱结点

void dfs(int x,int fa){		/// dfs计算每个结点及其子树所包括的黑结点和白结点的个数
    ll shu_b = 0;
    ll shu_w = 0;
    for(auto &it : G[x]){
        if(it != fa){
            fafa[it]=x;
            dfs(it,x);
            shu_b += down_black[it];
            shu_w += down_white[it];
        }
    }
    if(aa[x])shu_b ++;
    else shu_w ++;
    down_black[x] = shu_b;
    down_white[x] = shu_w;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        ll t = 2LL;
        cnt = 0;
        memset(down_black,0,sizeof(down_black));
        memset(down_white,0,sizeof(down_white));
        memset(fafa,0,sizeof(fafa));
        
        for(int i=1;i<=n;++i)G[i].clear();
        
        sum_black = sum_white = 0;
        for(int i=1;i<=n;++i){
            scanf("%d",&aa[i]);
            fa[i]=i;
            if(aa[i])sum_black ++;
            else sum_white ++;
        }
        
        for(int i=0;i<m;++i){
            int ta,tb;
            scanf("%d%d",&ta,&tb);
            edge[cnt].ta = ta;
            edge[cnt].tb = tb;
            edge[cnt++].w = t;
            t = (t * 2LL) % MOD;		/// 权重增加
        }
        for(int i=0;i<cnt;++i)			/// Kruskal
            add_edge(edge[i].ta,edge[i].tb,i);
            
        int root = 1;				/// 以1作为根节点跑树
        fs(root,-1);
        
        ll ans = 0LL;
        while(!E.empty()){
            int ta = edge[E.front()].ta;
            int tb = edge[E.front()].tb;
            int bh = E.front();
            ll quan = edge[bh].w;
            if(fafa[ta]==tb)swap(ta,tb);	/// 让ta在上,tb在下
            ans += ((sum_black - down_black[tb])*(down_white[tb])%MOD*quan)%MOD;
            ans%=MOD;
            ans += ((sum_white - down_white[tb])*(down_black[tb])%MOD*quan)%MOD;
            ans%=MOD;
            E.pop();
        }
        printf("%lld\n",(ans+MOD)%MOD);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值