水题挑战3:NOIP 2017 宝藏

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n n n 个深埋在地下的宝藏屋, 也给出了这 n n n 个宝藏屋之间可供开发的 m m m 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。

新开发一条道路的代价是:

L × K \mathrm{L} \times \mathrm{K} L×K

L L L代表这条道路的长度, K K K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

输入格式
第一行两个用空格分离的正整数 n , m n,m n,m,代表宝藏屋的个数和道路数。

接下来 m m m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1 − n 1-n 1n),和这条道路的长度 v v v

输出格式
一个正整数,表示最小的总代价。

【数据规模与约定】

对于 % 20 \%20 %20的数据: 保证输入是一棵树, 1 ≤ n ≤ 8 1 \le n \le 8 1n8 v ≤ 5000 v \le 5000 v5000 且所有的 v v v 都相等。

对于 % 40 \%40 %40的数据: 1 ≤ n ≤ 8 1 \le n \le 8 1n8 0 ≤ m ≤ 1000 0 \le m \le 1000 0m1000 v ≤ 5000 v \le 5000 v5000 且所有的 v v v都相等。

对于 % 70 \%70 %70的数据: 1 ≤ n ≤ 8 1 \le n \le 8 1n8 0 ≤ m ≤ 1000 0 \le m \le 1000 0m1000 v ≤ 5000 v \le 5000 v5000

对于 % 100 \%100% %100的数据: 1 ≤ n ≤ 12 1 \le n \le 12 1n12 0 ≤ m ≤ 1000 0 \le m \le 1000 0m1000 v ≤ 500000 v \le 500000 v500000

##SOLUTION

好吧其实这题还是有点难的,蒟蒻我当时还是在考场上拿到这题一脸懵逼。

看到这题的规模, n ≤ 12 n \le 12 n12,一般就会有几种想法:爆搜,状压(还有大佬写的模拟退火。。。是本弱弱不会的了) 。

由题意可得,我们求完之后会是一棵树,图上找一棵树。

首先,我们想到某一个点 i i i 作为这个图中树的根,对于其他点,我们关心其他点到起点的距离。

对于第一部分分,我们只需要对每一个根做一次树形DP即可。

之后变成了一个图,我们考虑状态压缩。

我们记 f [ S ] [ i ] f[S][i] f[S][i] , 表示我们对于状态为 S S S 的点集,最深层数为 i i i

然后我们可以稍微思考一下,对于点集 S S S ,他可以由自己的子点集 S 1 S1 S1 转移而来, ,于是,我们就有:

f [ S ] [ d ] = m i n f [ S 1 ] [ d − 1 ] + ( d − 1 ) × t r a n s f e r [ S 1 ] [ S ] , S 1 ⊂ S f[S][d] = min{f[S1][d-1] + (d-1) \times transfer[S1][S]}, S1\subset S f[S][d]=minf[S1][d1]+(d1)×transfer[S1][S],S1S

解释一下,这个式子相当于就是把 S 1 S1 S1 中的点,向外扩展一层,扩展完点集为 S S S

t r a n s f e r [ S 1 ] [ S ] transfer[S1][S] transfer[S1][S] 表示从 S 1 S1 S1 S S S 的最小价值。

我们考虑S中点 i i i, 不在 S 1 S1 S1 中, 那么从 i i i 转移到 S 1 S1 S1 的最小价值为:

W [ i ] [ S 1 ] = m i n ( e [ i ] [ j ] ) , j ∈ S 1 W[i][S1] = min(e[i][j]), j \in S1 W[i][S1]=min(e[i][j]),jS1

但是事实上这个W数组是不用写出来的,一次次加上去就好了。

然后

t r a n s f e r [ S 1 ] [ S ] = σ W [ i ] [ S 1 ] , i ∈ S , i ∉ S 1 transfer[S1][S] = \sigma W[i][S1] ,i\in S, i\notin S1 transfer[S1][S]=σW[i][S1],iS,i/S1

但是吧,不知道大家有没有这个困惑

#我们压根没考虑根节点!!

在我写完时候也有点纠结(纠结了好久,写完才想到

其实我们考虑一个点

我不会画画(偷懒),引用了大佬的博客GoldenPotato的OI世界

考虑这个图里,我们以图中给的为根,如果我们统计k=1时候没有加最右边的一个点,到k=2的状态时把这个点算进去,那不是距离根节点只有一个宝藏屋的边要 × 2 \times 2 ×2

显然是不对的。

但是我们可以在其他点为根的状态中,把这个点加进去,就一定会有最优的解。

所以我们的答案统计为: a n s = m i n f [ i ] [ ( 1 < < n ) − 1 ] , i ∈ [ 1 , n ] ans = min{f[i][(1<<n)-1], i \in [1,n]} ans=minf[i][(1<<n)1],i[1,n]

还有一个小技巧,就是枚举子集,

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

复杂度这样就从 n 2 × 4 n n^2\times 4^n n2×4n 降到 n 2 × 3 n n^2 \times 3^n n2×3n

于是乎,贴代码。

如果我哪里有疏漏,欢迎大家指正~

#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; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可爱の小公举

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值