【斯坦纳树】18牛客多校1G Steiner Tree

Source:牛客网暑期ACM多校训练营(第一场)
Problem:求斯坦纳树的个数
Idea:斯坦纳树的dp分成两个部分,其中第一部分 d p [ S ] [ u ] → d p [ S ] [ v ] dp[S][u] \to dp[S][v] dp[S][u]dp[S][v]通过最短路更新,不会算重复,定义这一部分得到的斯坦纳树为 f f f,最后一步为根的延伸。第二部分 d p [ S 0 ] [ u ] + d p [ S 0 ⊕ S ] [ v ] → d p [ S ] [ v ] dp[S0][u]+dp[S0 \oplus S][v] \to dp[S][v] dp[S0][u]+dp[S0S][v]dp[S][v],用上一步得到 f f f来合并得到新的斯坦纳树,定义为 g g g,只要固定住 S 0 S0 S0的一个点,就可以不重复地得到 g g g,而 g g g再回到第一部分更新 f f f

#include<bits/stdc++.h>
using namespace std;

#define I inline
#define fi first
#define se second
#define mp make_pair
#define pb push_back
typedef pair<int, int> PII;

const int N = 60;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;

int n;
bool done[N];
PII f[1<<12][N], g[1<<12][N];
vector<int> G[N];

PII operator*(const PII &A, const PII &B) { return {A.fi+B.fi, 1LL*A.se*B.se%MOD}; }

I void upd(PII &A, const PII &B) {
    if(A.fi > B.fi) A = B;
    else if(A.fi == B.fi) A.se = (A.se+B.se)%MOD;
}

priority_queue<PII, vector<PII>, greater<PII>> Q;

void dij(PII *g, PII *f) {
    for(int i = 0; i < n; i++) {
        if(g[i].fi != INF) Q.push({g[i].fi, i});
        done[i] = 0;
    }
    while(!Q.empty()) {
        PII t = Q.top(); Q.pop();
        int u = t.se;
        if(done[u]) continue;
        done[u] = 1;
        for(int v:G[u]) if(g[v].fi >= g[u].fi+1) {
            bool flag = g[v].fi>g[u].fi+1;
            upd(g[v], g[u]*mp(1, 1));
            upd(f[v], g[u]*mp(1, 1));
            if(flag) Q.push({g[v].fi, v});
        }
    }
}

I void work() {
    int m, k;
    if(scanf("%d%d%d", &n, &m, &k) == -1) exit(0);
    int all = (1<<k)-1;
    for(int u = 0; u < n; u++) {
        G[u].clear();
        for(int S = 0; S <= all; S++) f[S][u] = g[S][u] = {INF, 0};
        if(u < k) f[1<<u][u] = g[1<<u][u] = {0, 1};
    }

    while(m--) {
        int u, v; scanf("%d%d", &u, &v);
        G[u-1].pb(v-1), G[v-1].pb(u-1);
    }

    for(int S = 0; S <= all; S++) {
        for(int u = 0; u < n; u++) for(int S0 = (S-1)&S; S0; S0 = (S0-1)&S)
            if((S0&-S0) == (S&-S)) upd(g[S][u], f[S0][u]*g[S^S0][u]);
        dij(g[S], f[S]);
    }
    printf("%d\n", g[all][0].se);
}

int main() {
    for(;;) work();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值