1. 程式人生 > >NOIP2017提高組 Day2T2寶藏 狀壓DP

NOIP2017提高組 Day2T2寶藏 狀壓DP

題目就不說了吧。。
搜尋好像也能過,不過是啟發式才行。。何況比賽打得也是狀壓。。
其實不算很難吧,但是我比較菜所以掛了。
總是想著如何列舉,但是這題直接列舉轉移的話好像不大可做。
所以預處理出兩個狀態之間的轉移代價,即w[s1,s2]表示從s1轉移到s2的代價,保證s1&s2==0,即s1與s2不相交。
然後直接轉移就好了,複雜度是3^n*n^2,但是用列舉子集轉移的方法可以去除冗餘狀態,實際狀態遠遠達不到上界。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--) using namespace std; const int N=20; const int inf=1e9; const int M=4100; int n,m; int dis[N][N],bin[N]; int w[M][M],f[N][M],sta[M]; inline void pre() { fo(i,0,bin[n]-1) fo(j,0,bin[n]-1) w[i][j]=inf; fo(i,1,n) fo(j,1,n) if (i!=j&&dis[i][j]<inf) w[bin[i-1
]][bin[j-1]]=min(w[bin[i-1]][bin[j-1]],dis[i][j]); fo(s1,0,bin[n]-1) { int tmp=s1^(bin[n]-1),top=0; for(int s2=tmp;s2;s2=(s2-1)&tmp) sta[++top]=s2; while (top) { int s2=sta[top];top--; if (w[s1][s2]<inf) fd(i,n,1
) { if (s2&bin[i-1])break; fo(j,1,n) if (!(s2&bin[j-1])&&!(s1&bin[i-1])&&dis[j][i]<inf) w[s1|bin[j-1]][s2|bin[i-1]]=min(w[s1|bin[j-1]][s2|bin[i-1]],w[s1][s2]+dis[j][i]); } } } fo(s1,0,bin[n]-1) { int tmp=s1^(bin[n]-1); for(int s2=tmp;s2;s2=(s2-1)&tmp) if (w[s1][s2]<inf) fo(i,1,n) if (!(s1&bin[i-1])&&!(s2&bin[i-1])) w[s1|bin[i-1]][s2]=min(w[s1|bin[i-1]][s2],w[s1][s2]); } } inline void dp() { fo(i,1,n) fo(j,0,bin[n]-1) f[i][j]=inf; fo(i,1,n) f[1][bin[i-1]]=0; fo(i,1,n-1) fo(s,1,bin[n]-1-1) if (f[i][s]<inf) { int tmp=s^(bin[n]-1); for(int s1=tmp;s1;s1=(s1-1)&tmp) if (w[s][s1]<inf) f[i+1][s1|s]=min(f[i+1][s1|s],f[i][s]+w[s][s1]*i); } } int main() { freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout); scanf("%d%d",&n,&m); bin[0]=1; fo(i,1,n)bin[i]=bin[i-1]*2; fo(i,1,n) fo(j,1,n)dis[i][j]=inf; fo(i,1,m) { int x,y,z; scanf("%d%d%d",&x,&y,&z); dis[x][y]=min(dis[x][y],z); dis[y][x]=min(dis[y][x],z); } pre();dp(); int ans=inf; fo(i,1,n)ans=min(ans,f[i][bin[n]-1]); printf("%d\n",ans); }