版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013578420/article/details/81876111
题目大意:
给你一个带边权的无向图, n个点, m条边, 第i条边权值为w[i], 连接x[i]与y[i]。 对于原图的某一个有根生成树的价值定义为:
d数组表示点在有根树中的深度。
求所有可能有根生成树价值总和。
题目思路:
考虑有根树计数dp
f[i][j]表示点集为i的子树根为j的所有方案深度总和
g[i][j]表示点集为i的子树根为j的方案数
先说怎么算答案
考虑对于某一条边e={x,y,w}的贡献, 设S为全集
先枚举e这条边两端的子树长什么样
即先枚举x的子树为点集i, 则y的子树为点集S-i
考虑如果在有根树中, e这条有向边方向为x->y(父亲指向儿子), 则y的深度更大, 考虑f[S-i][y]表示的是子树S-i以y为根的所有方案深度总和,反过来想也可以说是以S-i中的某个点为根, y的深度的总和。 再考虑乘法原理, x的子树随便构造(因为y的深度更大), 答案为f[S-i][y] * g[i][x] * w。
同理如果是y->x, 答案为 f[i][x] * g[S-i][y] * w。
再说dp, 这是一个经典的有根树(有向树)计数的问题
初始值显然是f[
][i] = g[
][i] = 1
为了方便表示, 令h[i]表示二进制数i中有多少个1(点集i中有多少个点), c[x][y]表示原图中(x,y)之间的边数。
考虑f[i][j]的转移
即枚举一个i中不包含j的子集k, 考虑以k中某个元素l为根, 由于最终还是以j为根, 所有i-k中的点深度不会变化, 只需由乘法原理乘上k的方案数g[k][l], 而k中的点深度都会加1, 所以新增值为k中的点数成以原本k的方案数g[k][l], 最后再由乘法原理乘上g[i-k][j]。
g[i][j]的转移则比较简单
但单纯的这么枚举k会产生许多重复的计算
一个技巧是我们在枚举k的时候, 强制其一定包含了i-{j}的最小下标的那个元素即可, 原理有点类似于线性筛
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
#include <cmath>
#include <vector>
#define ll long long
#define db double
#define fi first
#define se second
#define pi pair<int, int >
#define ls (x << 1)
#define rs ((x << 1) | 1)
#define mid ((l + r) >> 1)
#define mp(x, y) make_pair((x), (y))
#define pb push_back
using namespace std;
const int N = 1 << 13;
const int M = (int)1010;
const ll mo = 1000000007;
int n, m, x[M], y[M], w[M];
ll f[N][13], g[N][13]; int c[13][13], h[N];
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++){
scanf("%d%d%d", x + i, y + i, w + i);
x[i] --; y[i] --;
c[x[i]][y[i]] ++; c[y[i]][x[i]] ++;
}
int S = (1 << n) - 1;
for (int i = 1; i <= S; i ++){
for (int j = i; j; j >>= 1) h[i] += j&1;
for (int j = 0; j < n; j ++){
if ((i & (1 << j)) == 0) continue;
int u = i ^ (1 << j), v = u&-u;
if (!u) {f[i][j] = g[i][j] = 1; break;}
for (int k = u; k; k = (k-1)&u){
if ((k & v) == 0) continue;
for (int l = 0; l < n; l ++){
if ((k & (1 << l)) == 0) continue;
(f[i][j] += (f[i^k][j] * g[k][l] % mo + (f[k][l] + h[k] * g[k][l] % mo) * g[i^k][j] % mo) * c[j][l] % mo) %= mo;
(g[i][j] += g[i^k][j] * g[k][l] % mo * c[j][l] % mo) %= mo;
}
}
}
}
ll ret = 0;
for (int j = 1; j <= m; j ++)
for (int i = 1; i <= S; i ++){
if ((i & (1 << x[j])) == 0) continue;
if (((S^i) & (1 << y[j])) == 0) continue;
(ret += (f[i][x[j]] * g[S^i][y[j]] % mo + f[S^i][y[j]] * g[i][x[j]] % mo) * w[j] % mo) %= mo;
}
printf("%lld\n", ret);
return 0;
}