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=1∑nj=1∑nd(i,j)×[ai=1∧aj=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
1≤T≤8
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(1≤n≤105,1≤m≤2×105)
【样例输入】
T
n,m
a
1
⋯
a
n
a_1\cdots a_n
a1⋯an
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
i−thedge,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)
disA−B−C−D−E(A,E)=1−2a(1−24)=(24−1)a<24a=disA−E(A,E)
如果再多枚举其他的点对之间的最小距离,就会发现他们都不会经过
e
d
g
e
A
−
E
edge_{A-E}
edgeA−E
对于这种无用的边,我们可能会想到删掉它。那么把所有的无用边都删除之后会发生什么?
容易想到,这可能是
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计算次数×Edgei的权值Edgei计算次数×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;
}