水題挑戰3: NOIP 2017 寶藏

參與考古挖掘的小明獲得了一份藏寶圖,藏寶圖上標出了 \(n\) 個深埋在地下的寶藏屋, 也給出了這 \(n\) 個寶藏屋之間可供開發的 \(m\) 條道路和它們的長度。html

小明決心親自前往挖掘全部寶藏屋中的寶藏。可是,每一個寶藏屋距離地面都很遠, 也就是說,從地面打通一條到某個寶藏屋的道路是很困難的,而開發寶藏屋之間的道路 則相對容易不少。c++

小明的決心感動了考古挖掘的贊助商,贊助商決定免費贊助他打通一條從地面到某 個寶藏屋的通道,通往哪一個寶藏屋則由小明來決定。git

在此基礎上,小明還須要考慮如何開鑿寶藏屋之間的道路。已經開鑿出的道路能夠 任意通行不消耗代價。每開鑿出一條新道路,小明就會與考古隊一塊兒挖掘出由該條道路 所能到達的寶藏屋的寶藏。另外,小明不想開發無用道路,即兩個已經被挖掘過的寶藏 屋之間的道路無需再開發。數組

新開發一條道路的代價是:spa

\(\mathrm{L} \times \mathrm{K}\)code

\(L\)表明這條道路的長度,\(K\)表明從贊助商幫你打通的寶藏屋到這條道路起點的寶藏屋所通過的 寶藏屋的數量(包括贊助商幫你打通的寶藏屋和這條道路起點的寶藏屋) 。htm

請你編寫程序爲小明選定由贊助商打通的寶藏屋和以後開鑿的道路,使得工程總代 價最小,並輸出這個最小值。blog

輸入格式
第一行兩個用空格分離的正整數 \(n,m\),表明寶藏屋的個數和道路數。開發

接下來 \(m\) 行,每行三個用空格分離的正整數,分別是由一條道路鏈接的兩個寶藏 屋的編號(編號爲 \(1-n\)),和這條道路的長度 \(v\)get

輸出格式
一個正整數,表示最小的總代價。

【數據規模與約定】

對於 \(\%20\)的數據: 保證輸入是一棵樹,\(1 \le n \le 8\)\(v \le 5000\) 且全部的 \(v\) 都相等。

對於 \(\%40\)的數據: \(1 \le n \le 8\)\(0 \le m \le 1000\)\(v \le 5000\) 且全部的 \(v\)都相等。

對於 \(\%70\)的數據: \(1 \le n \le 8\)\(0 \le m \le 1000\)\(v \le 5000\)

對於 \(\%100%\)的數據: \(1 \le n \le 12\)\(0 \le m \le 1000\)\(v \le 500000\)

SOLUTION

好吧其實這題仍是有點難的,蒟蒻我當時仍是在考場上拿到這題一臉懵逼。

看到這題的規模,\(n \le 12\),通常就會有幾種想法:爆搜,狀壓(還有大佬寫的模擬退火。。。是本弱弱不會的了) 。

由題意可得,咱們求完以後會是一棵樹,圖上找一棵樹。

首先,咱們想到某一個點\(i\) 做爲這個圖中樹的根,對於其餘點,咱們關心其餘點到起點的距離。

對於第一部分分,咱們只須要對每個根作一次樹形DP便可。

以後變成了一個圖,咱們考慮狀態壓縮。

咱們記 \(f[S][i]\) , 表示咱們對於狀態爲 \(S\) 的點集,最深層數爲 \(i\)

而後咱們能夠稍微思考一下,對於點集 \(S\) ,他能夠由本身的子點集 \(S1\) 轉移而來, ,因而,咱們就有:

\(f[S][d] = min{f[S1][d-1] + (d-1) \times transfer[S1][S]}, S1\subset S\)

解釋一下,這個式子至關於就是把\(S1\) 中的點,向外擴展一層,擴展完點集爲\(S\)

\(transfer[S1][S]\) 表示從 \(S1\)\(S\) 的最小价值。

咱們考慮S中點\(i\), 不在\(S1\) 中, 那麼從\(i\) 轉移到\(S1\) 的最小价值爲:

\(W[i][S1] = min(e[i][j]), j \in S1\)

可是事實上這個W數組是不用寫出來的,一次次加上去就行了。

而後

\(transfer[S1][S] = \sigma W[i][S1] ,i\in S, i\notin S1\)

可是吧,不知道你們有沒有這個困惑

咱們壓根沒考慮根節點!!

在我寫完時候也有點糾結(糾結了很久,寫完纔想到

其實咱們考慮一個點

我不會畫畫(偷懶),引用了大佬的博客GoldenPotato的OI世界

考慮這個圖裏,咱們以圖中給的爲根,若是咱們統計k=1時候沒有加最右邊的一個點,到k=2的狀態時把這個點算進去,那不是距離根節點只有一個寶藏屋的邊要\(\times 2\)

顯然是不對的。

可是咱們能夠在其餘點爲根的狀態中,把這個點加進去,就必定會有最優的解。

因此咱們的答案統計爲: \(ans = min{f[i][(1<<n)-1], i \in [1,n]}\)

還有一個小技巧,就是枚舉子集,

for(int s1=s; s1; s1=(s1-1)&s)

複雜度這樣就從\(n^2\times 4^n\) 降到 \(n^2 \times 3^n\)

因而乎,貼代碼。

若是我哪裏有疏漏,歡迎你們指正~

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define _(d) while(d(isdigit(ch=getchar())))
template <class T> void g(T&t){T x,f=1;char ch;_(!)ch=='-'?f=-1:f;x=ch-48;_()x=x*10+ch-48;t=f*x;}
typedef long long ll; 
const int N=14;
int n, m, inf, tr[1<<N][1<<N], e[N][N];ll f[N][1<<N];
int main(){
	g(n), g(m);
	memset(e, 63, sizeof(e)); inf = e[0][0];
	rep(i,1,m){
		int x, y, z; g(x), g(y), g(z);
		e[x][y] = e[y][x] = min( e[x][y], z );
	}
	
	rep(s, 0, (1<<n)-1){
		for(int s1=s; s1; s1=(s1-1)&s){
			int tmp= s ^ s1;
			bool flag=0;
			rep(i, 1, n){
				if( 1<<(i-1) & tmp ){
					int tt= inf;
					rep(j, 1, n){
						if( 1<<(j-1) & s1){
							tt= min( tt, e[i][j] );
						}
					}
					if( tt == inf ){
						flag = 1;
						break;
					}
					tr[s1][s] += tt;
				}
			}
			if( flag ){
				tr[s1][s] = inf;
			}
		}	
	}
	 
	memset(f, 63, sizeof(f));
	ll ans=f[0][0]; 
	rep(i, 1, n) f[1][1<<(i-1)] = 0;
	rep(i, 2, n){
		rep( s, 0, (1<<n)-1 )
			for(int s1=s; s1; s1=(s1-1) & s ){
				if(tr[s1][s]!=inf)
				f[i][s]=min(f[i][s], f[i-1][s1] + tr[s1][s]*(i-1) );
				
			}
	}
	rep(i, 1, n) ans=min(ans, f[i][(1<<n)-1]);
	printf("%lld\n",ans);
	return 0; 
}
相關文章
相關標籤/搜索