【NOIp2017】宝藏
题面
https://www.luogu.org/problem/P3959
题解
考场上写的最暴力的状压$dp$,计算量是$2e8$级别的,但是$Van$的评测机非常没有信仰,发现只有$80pts$(只比真暴力多了$10$分)。。。。
注意到层与层之间的转移是独立的。
设$F[i][S0][S1]$为当前是第$i$层,$S0$是$1$到$i-1$层的节点集合,$S1$是第$i$层的节点集合。它的最小代价是多少。
转移的时候枚举一个下一层的节点是啥,然后用这一层的和它连上去转移就行了。
#include<cstdio> #include<cstring> #include<iostream> #define ri register int #define S 540000 #define B 4096 #define N 12 #define INF 1000000007 using namespace std; inline int read() { int r=0,f=0; char c=getchar(); while (c<'0' || c>'9') f|=(c=='-'),c=getchar(); while (c>='0' && c<='9') r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } int n,m; int dis[N][N],pp[N],num[B]; int stk[S]; int f[S],g[N][S]; inline int trans(int s,int t) { return num[s]+num[t]*2; } inline void chkmin(int &a,int b) { if (b<a) a=b; } int main() { freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout); pp[0]=1; for (ri i=1;i<12;i++) pp[i]=pp[i-1]*3; num[0]=0; for (ri i=1;i<B;i++) { for (ri j=0;j<12;j++) if (i&(1<<j)) { num[i]=num[i-(1<<j)]+pp[j]; break; } } n=read(); m=read(); memset(dis,0x3f,sizeof(dis)); for (ri i=1;i<=m;i++) { int u=read()-1,v=read()-1,w=read(); if (dis[u][v]>w) dis[u][v]=dis[v][u]=w; } memset(f,0x3f,sizeof(f)); memset(g,0x3f,sizeof(g)); int U=(1<<n)-1; for (ri s=0;s<=U;s++) { int top=0; for (ri s0=s;s0;s0=(s0-1)&s) stk[++top]=s0; f[trans(s^U,0)]=0; while (top) { int s0=stk[top]; top--; for (ri i=0;i<n;i++) if (s0&(1<<i)) { int a=trans(s^U,s0); int b=trans(s^U,s0-(1<<i)); for (ri j=0;j<n;j++) if ((s^U)&(1<<j) && dis[i][j]<INF) { f[a]=min(f[a],f[b]+dis[i][j]); } break; } } } for (ri j=0;j<n;j++) g[0][trans(0,1<<j)]=0; for (ri i=0;i<n-1;i++) for (ri s=0;s<=U;s++) for (ri s0=s;s0;s0=(s0-1)&s) { int tot=((s^U)|(s0))^U; for (ri s1=tot;s1;s1=(s1-1)&tot) if (f[trans(s0,s1)]<INF) { chkmin(g[i+1][trans((s^U)|s0,s1)],g[i][trans(s^U,s0)]+f[trans(s0,s1)]*(i+1)); } } int ans=INF; for (ri i=1;i<n;i++) for (ri s=0;s<=U;s++) if (g[i][trans(s,U^s)]<ans) ans=g[i][trans(s,U^s)]; cout<<ans<<endl; }
然后就是一个神奇的优化,其实我们没必要记录当前层是什么,直接记录$S=S0|S1$即可,然后强制把它们连到第$i+1$层即可。
正确性对我来说可以形象的理解一下。
$\mbox{sto aysn orz}$
#include <cstdio> #define min(a,b) ((a)<(b)?(a):(b)) const int maxn=12,inf=1000000000; int e[maxn][maxn],f[maxn][1<<maxn],tr[1<<maxn][maxn]; int main(){ freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout); int n,m,u,v,w,ans; scanf("%d%d",&n,&m); for(int i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&w); --u;--v; if(!e[u][v] || e[u][v]>w)e[u][v]=e[v][u]=w; } for(int i=0;i<(1<<n);++i) for(int j=0;j<n;++j)if(i&(1<<j)) for(int k=0;k<n;++k)if(!(i&(1<<k)) && e[j][k]) if(!tr[i][k] || tr[i][k]>e[j][k])tr[i][k]=e[j][k]; for(int i=0;i<n;++i) for(int j=0;j<(1<<n);++j) f[i][j]=inf; for(int i=0;i<n;++i) f[0][1<<i]=0; for(int i=1;i<n;++i) for(int j=0;j<(1<<n);++j)if(f[i-1][j]!=inf){ int t=((1<<n)-1)^j; for(int k=t;k;k=(k-1)&t){ ans=0; for(int a=0;a<n;++a)if(k&(1<<a)){ if(!tr[j][a]){ans=-1;break;} else ans+=tr[j][a]*i; } if(ans>=0)f[i][j|k]=min(f[i][j|k],f[i-1][j]+ans); } } ans=inf; for(int i=0;i<n;++i)ans=min(ans,f[i][(1<<n)-1]); printf("%d\n",ans); return 0; }