1. 程式人生 > >【NOIP2017提高組 day2】寶藏

【NOIP2017提高組 day2】寶藏

題目

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述


題解

–這道題不是最小生成樹這道題不是最小生成樹這道題不是最小生成樹
因為修路的代價與它裡起點的距離有關,又因為最後的路徑一定組成了一棵樹
所以我們可以一層層的dp
設f[i][s]:到第i層時,已經連上的集合為s的最優解
轉移f[i][s|S]=min(f[i-1][s]+D[s][S]*(i-1))
(D[s][S]是把這兩個沒有交集的集合相連的最小代價)
D是可以由d預處理出來的(d[i][s]是把i連到s這個集合的最小代價)
直接列舉起點,再向下dp就好

小技巧:列舉一個集合的子集for(int i=s;i;i=(i-1)&s)


程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=15;

int n,m;
long long g[MAXN][MAXN];
long long f[MAXN][1<<12],d[MAXN][1<<12],D[1<<12][1<<
12]; long long ans=0x7f7f7f7f; int main(){ cin>>n>>m; memset(g,0x7f,sizeof(g)); for(int i=1;i<=m;i++){ int u,v; long long l; scanf("%d%d%lld",&u,&v,&l); g[u][v]=min(g[u][v],l); g[v][u]=min(g[v][u],l); } memset(d,0x7f,sizeof(d)); int u=(1<<n)-1; for(int
i=1;i<=n;i++){ for(int s=0;s<(1<<n);s++){ for(int j=1;j<=n;j++) if((s>>(j-1))&1){ d[i][s]=min(d[i][s],g[j][i]); } } } for(int s=0;s<(1<<n);s++) for(int S=s^u;S;S=(S-1)&(s^u)) for(int i=1;i<=n;i++) if((S>>(i-1))&1){ if(d[i][s]==d[0][0]){ D[s][S]=0; break; } D[s][S]+=d[i][s]; } for(int r=1;r<=n;r++){ memset(f,0x7f,sizeof(f)); f[1][1<<(r-1)]=0; for(int i=2;i<=n;i++) for(int s=0;s<(1<<n);s++) for(int S=s^u;S;S=(S-1)&(s^u)) if(f[i-1][s]!=f[0][0]&&D[s][S]) f[i][s|S]=min(f[i][s|S],f[i-1][s]+D[s][S]*(i-1)); for(int i=1;i<=n;i++) ans=min(ans,f[i][(1<<n)-1]); } cout<<ans; return 0; }