P10949 四叶草魔杖 题解

注意到题目中\(n\)的范围只有16,考虑状态压缩动态规划。
设dp[i]代表在仅保留状态i所代表的子图的情况下。 将能量全部传递为0所消耗的权值。诺子图的总权值u不为0,那么dp[i]=+inf。
首先初始化。

定义sum[i]代表仅保留状态i所代表的子图的权值和。
诺sum[i]=0,那么对i所代表的子图跑MST

现在考虑如何转移。即如何合并状态。

对于\(dp[i]\)\(j\)\(i\)的子集,且\(dp[j]!=+inf\)。转移方程为。

\[dp[i] = max(dp[i],dp[i \oplus j]+dp[j]) \]

#include <bits/stdc++.h>

typedef long long ll;

const ll maxn=17, maxm=16*15/2+1;
const ll inf=1e18;

using std::cin;
using std::cout;

struct Edge{
    ll u,v,w;
};

ll sum[1<<maxn], dp[1<<maxn];
Edge e[maxm];
ll w[maxn], s[maxn];
ll n,m;

ll count(ll x) {
    ll cnt=0;
    while(x) {
        if(x&1) {
            ++cnt;
        }
        x>>=1;
    }
    return cnt;
}

ll find(ll x) {
    if(s[x]==x) {
        return x;
    }
    return s[x]=find(s[x]);
}

ll mst(ll x) {
    ll tot=count(x)-1,cnt=0,sum=0;
    std::iota(s+1,s+1+n,1);

    for(ll i=1;i<=m && cnt < tot;++i) {
        ll u=e[i].u,v=e[i].v,w=e[i].w;
        if(!(x & 1<<(u-1)) || !(x & 1<<(v-1))) {
            continue;
        }
        if(find(u)==find(v)) {
            continue;
        }
        ++cnt;
        s[find(u)]= find(v);
        sum+=w;
    }
    return (cnt==tot)? sum:inf;
}

int main() {
    freopen("input","r",stdin);

    cin>>n>>m;

    for(ll i=1;i<=n;++i) {
        cin>>w[i];
    }
    for(ll i=1;i<=m;++i) {
        cin>>e[i].u>>e[i].v>>e[i].w;
        ++e[i].u;
        ++e[i].v;
    }
    std::sort(e+1,e+1+m, [](const Edge &a,const Edge &b){return a.w<b.w;});
    sum[0]=0;
    for(ll i=0;i<(1<<n);++i) {
        for(ll j=0;j<n;++j) {
            if(i & (1<<j)) {
                continue;
            }
            sum[i | (1<<j)] = sum[i]+w[j+1];
        }
    }

    for(ll i=0;i<(1<<n);++i) {
        if(sum[i]==0) {
            ll res=mst(i);
            if(res==inf) {
                dp[i]=inf;
                continue;
            }
            dp[i]=res;
        }else{
            dp[i]=inf;
        }
    }

    for(ll i=1;i<(1<<n);++i) {
        //尝试计算dp[i];
        if(sum[i]!=0) {
            continue;
        }
        for(ll j=i;j;j=(j-1)&i) {
            if(sum[j]!=0 || dp[j]==inf) {
                continue;
            }
            dp[i]=std::min(dp[i],dp[i^j]+dp[j]);
        }
    }

    if(dp[(1<<n)-1]==inf) {
        puts("Impossible");
        return 0;
    }

    printf("%lld\n",dp[(1<<n)-1]);
    return 0;
}

posted @ 2026-06-26 15:14  txp2025  阅读(3)  评论(0)    收藏  举报