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;
}

浙公网安备 33010602011771号