链接
http://acm.hdu.edu.cn/showproblem.php?pid=5739
题意
给一个有n个节点和m条边的无向图,每个点有自己的点权wi,Gi的定义是删除i点形成的子图。
Gi的权值计算方法为
- 若为连通的图,是每个点的点权的乘积。
- 不连通,每个连通图或者孤立点的和。
最后求
S=∑ni=1Gi∗i
解析
首先我想说说ACM-ICPC程序设计系列 图论及应用。模板代码有坑啊,坑的一手好逼。
接下来,说说思路,根据题意,可以判断主要求点双连通分量,求出割点,将不是割点的点进行缩点。之后就形成了一棵树,在树上面进行计算就方便多了。遍历新图,依次遍历割点、缩点和孤立节点,记录它们整个块的权值。
计算。
1、若为割点,但不是根节点,整个连通块权值除以以他为根的子树的权值,然后在加该及节点的每个子树的权值;若是根节点直接加他的子树的权值。
2、若不是割点,直接整个连通块的权值除以该接点的权值。
注意:除法的取模,用费马小定理。
ans=(p/q)modt=p∗qt−2
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 200000+5;
const int mod = 1000000000+7;
int n, m;
vector<int>p[maxn], V[maxn], E[maxn];
int w[maxn], iw[maxn];
int low[maxn], dfn[maxn];
bool flag[maxn], vis[maxn];
int st[maxn];
int pre[maxn];
int stk, sig, bcc_cnt;
int color[maxn];
int dp[maxn];
void init() {
memset(dfn, 0, sizeof(dfn));
memset(pre, -1, sizeof(pre));
stk = 0;
sig = 0;
bcc_cnt = n;
memset(flag, false, sizeof(flag));
memset(vis, false, sizeof(vis));
memset(color, 0, sizeof(color));
}
int pow(int a, int b) {
int res = 1;
while (b) {
if (b&1)
res = 1LL*res*a%mod;
a = 1LL*a*a%mod;
b >>= 1;
}
return res;
}
void tarjan(int now, int pre) {
dfn[now] = low[now] = ++sig;
st[++stk] = now;
int son = 0;
for (int i=0; i<p[now].size(); i++) {
int v = p[now][i];
son++;
if (v == pre)
continue;
if (!dfn[v]) {
tarjan(v, now);
low[now] = min(low[now], low[v]);
if (low[v] >= dfn[now]) { // now不为树根时, low[v] >= dfn[now], 就说明now是割点。
bcc_cnt++;
flag[now] = true;
w[bcc_cnt] = 1;
V[bcc_cnt].clear();
do {
w[bcc_cnt] = 1LL*w[bcc_cnt]*w[st[stk]]%mod;
V[bcc_cnt].push_back(st[stk]);
}while (st[stk--] != v);
w[bcc_cnt] = 1LL*w[bcc_cnt]*w[now]%mod;
V[bcc_cnt].push_back(now);
}
}
else
low[now] = min(low[now], dfn[v]);
}
if (pre < 0 && son == 1) //now为树根,但是它的子树必须超过一个。
flag[now] = false;
}
void DFS(int u, int p, int fa) {
color[u] = fa;
vis[u] = true;
pre[u] = p;
dp[u] = w[u];
for (int i=0; i<E[u].size(); i++) {
int v = E[u][i];
if (v == p)
continue;
DFS(v, u, fa);
dp[u] = 1LL*dp[u]*dp[v]%mod;
}
if (u > n) {
for (int i=0; i<V[u].size(); i++)
{
vis[V[u][i]] = true;
color[V[u][i]] = fa;
}
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
init();
for (int i=1; i<=n; i++) {
p[i].clear();
scanf("%d", &w[i]);
iw[i] = pow(w[i], mod-2);
}
for (int i=0; i<m; i++) {
int u, v;
scanf("%d%d", &u, &v);
p[u].push_back(v);
p[v].push_back(u);
}
for (int i=1; i<=n; i++) {
if (!dfn[i])
tarjan(i, -1);
}
for (int i=1; i<=bcc_cnt; i++)
E[i].clear();
for (int i=n+1; i<=bcc_cnt; i++) {
for (int j=0; j<V[i].size(); j++) {
int v = V[i][j];
if (flag[v]) {
E[i].push_back(v);
E[v].push_back(i);
w[i] = 1LL*w[i]*iw[v]%mod;
}
}
}
int ans = 0;
for (int i=1; i<=n; i++) { //遍历割点
if (!vis[i] && flag[i]) {
DFS(i, -1, i);
ans = (ans+dp[i])%mod;
}
}
for (int i=n+1; i<=bcc_cnt; i++) { // 遍历没有割点的连通块
if (!vis[i]) {
DFS(i, -1, i);
ans = (ans+dp[i])%mod;
}
}
for (int i=1; i<=n; i++) { //遍历孤立节点
if (!vis[i]) {
DFS(i, -1, i);
ans = (ans+dp[i])%mod;
}
}
int res = 0;
for (int i=1; i<=n; i++) {
int fa = color[i];
int temp = ans;
ans = (ans-dp[fa]+mod)%mod;
if (flag[i]) {
int t = 1LL*dp[fa]*pow(dp[i], mod-2)%mod;
if (i != fa)
ans = (ans+t)%mod;
for (int j=0; j<E[i].size(); j++) {
int v = E[i][j];
if (pre[v] == i)
ans = (ans + dp[v])%mod;
}
}
else
{
if (i != fa)
ans = (ans+1LL*dp[fa]*iw[i]%mod)%mod;
}
res = (res + 1LL*i*ans)%mod;
ans = temp;
}
printf("%d\n", res);
}
return 0;
}