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

題目大意:
給你一個帶邊權的無向圖, n個點, m條邊, 第i條邊權值爲w[i], 連接x[i]與y[i]。 對於原圖的某一個有根生成樹的價值定義爲:

e=x,ywemax(dx,dy)

d數組表示點在有根樹中的深度。
求所有可能有根生成樹價值總和。(n12,m1000,wi5000)

題目思路:
考慮有根樹計數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[2i ][i] = g[2i ][i] = 1
爲了方便表示, 令h[i]表示二進制數i中有多少個1(點集i中有多少個點), c[x][y]表示原圖中(x,y)之間的邊數。
考慮f[i][j]的轉移

f[i][j]=ki{j}lk(f[ik][j]g[k][l]+(f[k][l]+h[k]g[k][l])g[ik][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]=ki{j}lkg[ik][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;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章