[Nowcoder 2018ACM多校第九场B] Enumeration not optimization

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013578420/article/details/81876111

题目大意:
给你一个带边权的无向图, n个点, m条边, 第i条边权值为w[i], 连接x[i]与y[i]。 对于原图的某一个有根生成树的价值定义为:

e = x , y w e m a x ( d x , d y )

d数组表示点在有根树中的深度。
求所有可能有根生成树价值总和。 ( n 12 , m 1000 , w i 5000 )

题目思路:
考虑有根树计数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[ 2 i ][i] = g[ 2 i ][i] = 1
为了方便表示, 令h[i]表示二进制数i中有多少个1(点集i中有多少个点), c[x][y]表示原图中(x,y)之间的边数。
考虑f[i][j]的转移

f [ i ] [ j ] = k i { j } l k ( f [ i k ] [ j ] g [ k ] [ l ] + ( f [ k ] [ l ] + h [ k ] g [ k ] [ l ] ) g [ i k ] [ j ] ) c [ j ] [ l ]

即枚举一个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]的转移则比较简单
g [ i ] [ j ] = k i { j } l k g [ i k ] [ j ] g [ k ] [ l ] c [ j ] [ l ]

但单纯的这么枚举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;

}

猜你喜欢

转载自blog.csdn.net/u013578420/article/details/81876111