1. 程式人生 > 實用技巧 >[Noip2017]寶藏

[Noip2017]寶藏

[Noip2017]寶藏

一.前言

​ 這道題的轉移是真的有點怪……題目連結

二.思路

​ 嘛,很明顯是讓你造一個生成樹使得代價最小,看到 \(1<=n<=12\) 的時候就應該明白這道題的瘋狂暗示了,狀壓 DP 嘛,TG老朋友了。

​ 觀察到代價的計算,會發現一個 \(K\) 是一個至關重要的點,不把這個處理好了,就做不了。而這個 K也可以理解為深度。那麼將狀態設為 \(f_{ij}\) 為用狀態為 j 的集合 \(S\) 構造出了一個最深深度為 i 的代價最小的生成樹。

​ 然後很多題解所謂的"顯而易見"的方程我反正是不能一下子想出來的,所以我會寫的麻煩一些。計算 \(f_{ij}\)

那麼

  • 肯定存在一個生成樹代表 \(f_{ij}\) 的最優狀態,它的點集設為 \(S\)
  • 這顆樹一定有至少一個深度為 i 的點,這些點的點集我設為 \(u\)
  • 點集中所有點的父親深度都為 \(i-1\)
  • 設一個樹 \(p\) 為 這個生成樹 刪去 \(u\) 後的模樣,其狀態為 q

於是 p 必然代表 \(f_{i-1~q}\) 的最優狀態(措辭有點不嚴謹了見諒),抽象的說就是 最優減去必要的最少後還是最優,建議反覆理解。於是可以反著來。

​ 列舉最深深度 i 從小到大,再列舉狀態 j 和 j 的子集 k,可以得到轉移方程

\[f_{ij}=min(f_{ij},f_{i-1~k}+trans_{jk}*(i-1)) \]

其中 \(trans\) 為狀態轉移的最短距離和。實際上列舉子集 k 就是在找 u(u=j^k) ,由於 i 從小到大之前的狀態都已經是最優,放心轉移就好。

​ 然後初始化就是最深為1,狀態只有一個點的時候老闆幫忙,無花費,為 0。

​ 這題我當時覺得很奇怪的一點就在於,每一個狀態在沒有算完的時候是可能不合法的,因為 \(trans\)不會全部都接到最深的點,列舉的集合也不會都是最外圍的點,但是最後最小的一定是合法的就對了。最優對最優的時候一定全部接到最深的點上,因為定義就是這麼定義的,找的集合是最優時候的(上面的u)如果接其他點就不會是最優……(有點因果律)不像其他的 DP 題,很多時候只是不是最優,但是不會是不合法。

​ 以上。雖並非什麼真知灼見,卻也還是若有所思。

三.CODE

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<cstring>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
const int MAXN=1<<12,inf=707406378;
int n,maxn,m;
int trans[MAXN][MAXN],dis[13][13],f[13][MAXN];
int main(){
	memset(dis,127/3,sizeof(dis));
	memset(f,127/3,sizeof(f));
	n=read();m=read();
	maxn=1<<n;
	for(int i=1,x,y,v;i<=m;++i){
		x=read()-1;y=read()-1;v=read();
		dis[x][y]=dis[y][x]=min(dis[x][y],v);
	}
	for(int i=0;i<maxn;++i){
		for(int j=i-1;j;j=(j-1)&i){
			int k=i^j,stj[13],topj=0,topk=0,stk[13];
			for(int p=0;p<n;++p){
				if(j&(1<<p))stj[++topj]=p;
				if(k&(1<<p))stk[++topk]=p;
			}
			while(topk){
				int cnt=inf;
				for(int p=topj;p>0;--p){
					cnt=min(cnt,dis[stk[topk]][stj[p]]);
				}
				if(cnt!=inf)trans[i][j]+=cnt;
				else{
					trans[i][j]=inf;
					break;
				} 
				topk--;
			}
		}
	}
	int ans=inf;
	for(int i=0;i<n;++i)f[1][1<<i]=0;
	ans=f[1][maxn-1];
	for(int k=2;k<=n;++k){
		for(int i=1;i<maxn;++i){
			for(int j=i-1;j;j=(j-1)&i){
				if(trans[i][j]!=inf&&f[k-1][j]!=inf)
					f[k][i]=min(f[k][i],f[k-1][j]+(k-1)*trans[i][j]);
			}
		}
		ans=min(ans,f[k][maxn-1]);
	}
	cout<<ans;
	return 0;
} 

trans整的比較粗糙hhhh