NOIP2017 - 寶藏
阿新 • • 發佈:2018-03-01
邊距 ++ sca mes %d cnblogs 集合 mat str 其中\(w[s_1][s_2]\)表示將\(s_2\)接在\(s_1\)上的最小花費,預處理一下即可。
LibreOJ鏈接
Description
給出一個\(n(n\leq12)\)個點\(m(m\leq1000)\)條邊的帶權無向圖,求該圖的一棵生成樹,使得其邊權×該邊距根的深度之和最小。
Solution
既然\(n\leq12\),可以猜測是狀壓DP。
定義\(f[dpt][s][s_1]\)表示一棵深度為\(dpt\),點集為\(s\),最深的(深度為\(dpt\))的點的集合為\(s_1\)的生成樹的權值。我們考慮給\(s_1\)接上一些點\(s_2\),從而轉移為\(f[dpt+1][s|s_2][s_2]\)。轉移方程為:\[f[dpt+1][s|s_2][s_2]=min\{ f[dpt][s][s_1]+w[s_1][s_2] \} \space (s_1\in s,s_2\in \complement_U^s )\]
Code
//「NOIP2017」寶藏 #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int const N=15; int const S=1<<12; int const INF=0x3F3F3F3F; int n,m,ed[N][N]; int U; int w[S][S],f[2][S][S]; void calW() { memset(w,0x3F,sizeof w); for(int s1=0;s1<=U;s1++) w[s1][0]=0; for(int s1=0;s1<=U;s1++) for(int i=0;i<n;i++) { int s2=1<<i; if(s1&s2) continue; for(int j=0;j<n;j++) if((s1>>j)&1) w[s1][s2]=min(w[s1][s2],ed[i+1][j+1]); } for(int s1=0;s1<=U;s1++) for(int s2=1;s2<=U;s2++) { if(s1&s2) continue; for(int i=1;i<=s2;i<<=1) if(s2&i) w[s1][s2]=min(w[s1][s2],w[s1][s2^i]+w[s1][i]); } } int main() { scanf("%d%d",&n,&m); U=(1<<n)-1; if(n==1) {puts("0"); return 0;} memset(ed,0x3F,sizeof ed); for(int i=1;i<=m;i++) { int u,v,c; scanf("%d%d%d",&u,&v,&c); ed[u][v]=ed[v][u]=min(ed[u][v],c); } calW(); int c=0; int ans=INF; memset(f,0x3F,sizeof f); for(int i=1;i<=U;i<<=1) f[c][i][i]=0; for(int dpt=1;dpt<=n;dpt++) { c^=1; for(int s=0;s<=U;s++) for(int s2=U^s;s2;s2=(s2-1)&(U^s)) { int res=INF; for(int s1=s;s1;s1=(s1-1)&s) if(f[c^1][s][s1]<INF&&w[s1][s2]<INF) res=min(res,f[c^1][s][s1]+w[s1][s2]*dpt); f[c][s|s2][s2]=res; } for(int s2=0;s2<=U;s2++) ans=min(ans,f[c][U][s2]); } printf("%d\n",ans); return 0; }
P.S.
初始的DP數組要清\(\infty\),而不是\(0\)。
DP數組需要滾動,否則會MLE
。
我這個做法在LOJ上需要稍微卡一下常,第52行的if
就是卡常用的。
NOIP2017 - 寶藏