洛谷P3959 宝藏(状压dp)

时间:2021-10-29 20:23:36

传送门

为什么感觉状压dp都好玄学……FlashHu大佬太强啦……

设$f_{i,j}$表示当前选的点集为$i$,下一次要加入的点集为$j$时,新加入的点和原有的点之间的最小边权。具体的转移可以枚举$i$,然后枚举$i$的补集$j$,找出$j$的$lowbit_j$

那么转移就是$$f_{i,j}=min\{f_{i,j-lowbit_j}+min\{dis[lowbit_j][i]\}\}$$

据说这一部分的复杂度是$O(3^nn)$,因为$n$元素的所有子集的大小之和是$3^n$(然而我并不会证)

然后考虑把整张图给分层,设$g_{l,i}$表示总层数为$l$,已选点集为$i$时的最小答案,然后转移就是

$$g_{l,i}=\sum_{j\in i}g_{l-1,i-j}+l*f_{i-j,j}$$

据说这个复杂度也是$O(3^nn)$,最后$max\{g_{l,2^n-1}\}$就是答案了

 //minamoto
#include<iostream>
#include<cstdio>
#include<cstring>
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,:;}
inline int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while(!isdigit(ch=getc()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getc());res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
const int N=,M=,inf=0x01010101;
int a[N][N],f[M][M],g[N][M],ne[M],lg[M];
int n,m,S,s,l,x,y,v;
int main(){
// freopen("testdata.in","r",stdin);
memset(a,,sizeof(a));
memset(g,,sizeof(g));
n=read(),m=read();
S=(<<n)-;
for(int i=;i<n;++i) lg[<<i]=i;
while(m--){
x=read(),y=read(),v=read();
if(a[--x][--y]>v) a[x][y]=a[y][x]=v;
}
for(int i=;i<=S;++i){
v=;
for(int j=s=S^i;j;j=(j-)&s)
ne[j]=v,v=j;
//枚举i的补集,同时为了保证转移正确性要反着转移才行
for(int j=v;j;j=ne[j]){
x=lg[j&-j],v=inf;
for(y=;y<n;++y)
if(<<y&i) cmin(v,a[x][y]);
f[i][j]=f[i][j^(j&-j)]+v;
}
}
for(int i=;i<=S;i<<=) g[][i]=;
for(int l=;l<n;++l)
for(int i=;i<=S;++i)
for(int j=i;j;j=(j-)&i)
cmin(g[l][i],g[l-][i^j]+f[i^j][j]*l);
int v=0x3f3f3f3f;
for(int l=;l<=n;++l) cmin(v,g[l][S]);
printf("%d\n",v);
return ;
}