水题挑战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; 
}
相关文章
相关标签/搜索