1. 程式人生 > >[Luogu3959] [NOIP2017] 寶藏 Treasure [狀態壓縮+子集+dp/搜尋+剪枝/模擬退火]

[Luogu3959] [NOIP2017] 寶藏 Treasure [狀態壓縮+子集+dp/搜尋+剪枝/模擬退火]

[ L i n k \frak{Link} ]
注意:洛谷題解裡面有很大一部分題解是錯誤的
具體可以用討論中的hack資料自行驗證。


點數很特殊。
最開始的想法是類似最小生成樹那樣的東西,不過這個“最小”定義實在有點模糊。

注意到資料範圍。n的範圍十分特殊,考慮一下。
按理來說不是搜尋就是狀態壓縮了,而且狀壓的可能性更大一點。
不過還是先考慮搜尋吧。


搜尋

首先列舉起點,然後逐個跑最小生成樹?
要注意的是,邊權不是固定的,和之前的方案有關。所以最小生成樹是跑不了了。
嘗試列舉全排列,同時每一步還要列舉新的點是哪個點拓展出來的。
這樣就能騙不少分了(

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int
n,m,dis[15][15]={},dep[15]={}; int pt[15][15]={}; int mn[15]={}; long long ans=0x3f3f3f3f; bool vis[15]={}; int stk[15]={}; inline void dfs(register int step,register long long sum) { if(sum>=ans) return; if(step==n) { ans=min(ans,sum); return;} for(register int i,ti=1;ti<=stk[0];++ti) { i=
stk[ti]; for(register int v,j=1;j<=pt[i][0];++j) { v=pt[i][j]; if(vis[v])continue; vis[v]=1, dep[v]=dep[i]+1; stk[++stk[0]]=v; dfs(step+1,sum+1ll*dis[i][v]*dep[v]); vis[v]=0; --stk[0]; } } } int main() { memset(mn,0x3f,sizeof(mn)); memset(dis,0x3f,sizeof(dis)); scanf("%d%d",&n,&m); for(register int i=1;i<=n;++i)dis[i][i]=0; for(register int u,v,w,i=1;i<=m;++i) { scanf("%d%d%d",&u,&v,&w); if(dis[u][v]==0x3f3f3f3f) { pt[u][++pt[u][0]]=v; pt[v][++pt[v][0]]=u; } if(w<dis[u][v]) { dis[u][v]=dis[v][u]=w; mn[u]=min(mn[u],w); mn[v]=min(mn[v],w); } } stk[0]=1; for(register int i=1;i<=n;++i) { vis[i]=1; dep[i]=0; stk[1]=i; dfs(1,0); vis[i]=0;} printf("%d",ans); return 0; }

沒有特意剪枝,剪枝AC的程式碼可以參考洛谷題解


模擬退火

在Prim貪心的基礎上隨機接受非最短邊。
具體見洛谷題解


狀態壓縮

狀壓的根本在於按(被壓縮的)層轉移。
初步考慮 f ( S ) \frak{f(S)} 表示 S \frak{S} 裡面的點都被選入,此時的最小代價。
轉移的影響因素有兩個,一個是轉移邊的權值,一個是轉移點的層數
第二個是動態變化的。於是狀態變為 f ( i , S ) \frak{f(i,S)}
所以列舉 i \frak{i} S \frak{S} ,然後列舉 S \frak{S&#x27;} 表示要放在 i + 1 \frak{i+1} 層的點集。
複雜度是 Θ ( n 4 n ) \frak{\Theta(n·4^n)} 。實際上裡面的剪枝剪掉了很多重複的狀態,仔細考慮一下?

實際上因為 S \frak{S&#x27;} S \frak{S} 補集的子集也就是全集的子集的子集,
列舉 S \frak{S} 的一層複雜度可以無視掉,複雜度實際上就是列舉全集的子集的子集的複雜度。

考慮一個元素數量為 n \frak{n} 的集合,它的元素個數為 x \frak{x} 的子集數量為 C n x \frak{C_n^x}
且元素個數為 x \frak{x} 的集合的所有子集數量為 2 x \frak{2^x}
綜上所述,元素個數為 n \frak{n} 的集合的子集的子集數量為 x = 1 n C n x 2 x \frak{\sum\limits_{x=1}^{n}C_n^x2^x}
這個形式應該挺熟悉的,就是二項式定理那個 r = 1 n C n r a n r b r \frak{\sum\limits_{r=1}^nC_n^ra^{n-r}b^r} a = 2 , b = 1 \frak{a=2,b=1}
所以數量就是 ( 2 + 1 ) n = 3 n \frak{(2+1)^n}=3^n

綜上所述複雜度為 Θ ( n 3 n ) \frak{\Theta(n·3^n)}

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
using namespace std;
int n,m,Ans=0x3f3f3f3f;
int F[15][5000]={};
int dis[15][15]={};
int cost[15][5000]={};
int to[5000][5000]={};
void out(int x)
{
	while(x)
	{
		if(x&1)putchar('1');
		else putchar('0');
		x>>=1;
	}cout<<" ";
}
int main()
{
	memset(dis,0x3f,sizeof(dis));
	memset(cost,0x3f,sizeof(cost));
	scanf("%d%d",&n,&m);
	for(register int u,v,w,i=1;i<=m;++i)
	{
		scanf("%d%d%d",&u,&v,&w);
		if(w<dis[u][v])dis[u][v]=dis[v][u]=w;
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<(1<<n);++j)
		{
			if(j&(1<<i-1))continue;
			for(int k=1;k<=n;++k)
			{
				if(j&(1<<k-1))cost[i][j]=min(cost[i][j],dis[i][k]);
			}
		}
	}
	for(int i=0;i<(1<<n);++i)
	{
		for(int j=0;j<(1<<n);++j)
		{
			if(i&j)continue;
			for(int k=1;k<=n;++k)
			{
				if(i&(1<<k-1))to[i][j]=min(to[i][j]+cost[k][j],0x3f3f3f3f);
			}
		}
	}
	memset(F,0x3f,sizeof(F));
	for(int i=1;i<=n;++i)F[1][1<<i-1]=0;
	for(register int f=1;f<n;++f)
	{
		for(register int j=0;j<(1<<n);++j)
		{
			if(F[f][j]==0x3f3f3f3f)continue;
			for(register int k=0