P3959
SOL
不错的一道简单树论状压dp
-
一句话题解:状压枚举每一层节点计算贡献,dp出答案。(或者深搜)
-
说具体一点,发现贡献和深度有关,加上巨小无比的数据范围,我们可以记录前一颗树的节点和深度,枚举下一层地节点,把树一层一层地填出来。
-
只需要预处理出 : 把点集S1和S2连边的边权和最小值 \text{把点集S1和S2连边的边权和最小值} 把点集S1和S2连边的边权和最小值 顺便判一下合法性。
-
枚举子集,有一个结论: 2 n 2^n 2n个集合的子集个数和为 3 n 3^n 3n。
-
时间复杂度: O ( n 3 n ) O(n3^n) O(n3n)
- 据说深搜跑得贼快?
CODE
#include<bits/stdc++.h>
using namespace std;
#define sf scanf
#define ri register int
#define in red()
#define gc getchar()
#define cs const
inline int red(){
int num=0,f=1;char c=gc;
for(;!isdigit(c);c=gc)if(c=='-')f=-1;
for(;isdigit(c);c=gc)num=(num<<1)+(num<<3)+(c^48);
return num*f;
}
cs int lim=1<<12,inf=0x3f3f3f3f;
int f[20][lim],g[lim][lim],state,n,m,stk[lim<<1],top;
inline void ckmin(int &a,cs int &b){
if(a>b)a=b;
}
#define lb(x) (x&(-x))
signed main(){
// freopen("data.in","r",stdin);
n=in;m=in;
state=(1<<n)-1;
memset(f,0x3f,sizeof(f));
memset(g,0x3f,sizeof(g));
for(ri i=1;i<=m;++i){
int x=in,y=in,z=in;
--x;--y;
ckmin(g[1<<x][1<<y],z);
ckmin(g[1<<y][1<<x],z);
}
for(ri i=0;i<n;++i){
for(ri j=0;j<=state;++j){
g[j][1<<i]=min(g[j^lb(j)][1<<i],g[lb(j)][1<<i]);
}
}
for(ri i=1;i<=state;++i){
int j=(~i)&state;
top=-1;
for(ri t=j;t;t=(t-1)&j){
stk[++top]=t;
}
for(ri k=top;k>=0;--k){
int t=stk[k];
if(g[i][lb(t)]^inf&&g[i][t^lb(t)]^inf){
g[i][t]=g[i][t^lb(t)]+g[i][lb(t)];
}
}
}
for(ri i=0;i<n;++i)f[0][1<<i]=0;
for(ri i=1;i<n;++i){
for(ri j=1;j<=state;++j){
for(ri t=j;t;t=(t-1)&j){
if(g[t][j^t]^inf&&f[i-1][t]^inf){
ckmin(f[i][j],f[i-1][t]+i*g[t][j^t]);
}
}
}
}
int ans=inf;
for(ri i=0;i<n;++i)ans=min(ans,f[i][state]);
cout<<ans<<'\n';
return 0;
}