一道状压板子题题解By 云岁月书 2020/7/16

Posted 2003-10-08

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道状压板子题题解By 云岁月书 2020/7/16相关的知识,希望对你有一定的参考价值。

原题:

机器人刚刚探查归来,探险队员们突然发现自己的脚下出现了一朵朵白云,把他们托向了空中。一阵飘飘然的感觉过后,队员们发现自己被传送到了一座空中花园。 “远道而来的客人,我们是守护Nescafe之塔的精灵。如果你们想拜访护法和圣主的话,就要由我们引路。因此,你们是不是该给我们一点礼物呢?” 队员们往远处一看,发现花园中有N个木箱,M条柔软的羊毛小路,每条小路的两个端点都是木箱,并且通过它需要ti的时间。队员们需要往每个木箱中放一份礼物,不过由于精灵们不想让花园被过多地踩踏,因此运送礼物的任务最多只能由两位探险队员完成。 两位探险队员同时从1号木箱出发,可以在任意木箱处结束运送。运送完毕时,每只木箱应该被两位队员中至少一人访问过。运送任务所用的时间是两人中较慢的那个人结束运送任务的时间。请问完成运送礼物的时间最快是多少呢?

题目大意:

给你一个有边权的无向图,设其的点集 \(U\),求从点 \(1\) 出发的两条路径 \(d_1,d_2\),使得 \({d_1}\cup{d_2} = U\)

数据范围:

$ 1 \le N \le 18,1 \le wi\le 1000$

思考过程:

看题目猜测是图论题,再看数据范围,肯定是状态压缩 \(DP\)

然后我们开始设计状态。

我们先考虑一下在 \(DP\) 的过程中有哪些东西需要记录。

首先,一定需要传递的是当前过程中遍历了多少点。

其次,我们还需要传递当前节点处已用了多少时间。

最后,还有我们走到当前节点遍历了哪些节点。

难点:是两个人在走。

这让我想到了洛谷的 P1006 ,为了解决它我们把状态设计成了四维(当然也可以三维,甚至二维。甚至可以用网络流.。),两个人的位置都准确记录了。

但是仔细考虑一下,不难发现上述设计的状态很难在规定的空间(\(256 MB\))内完成 \(DP\) 数组的定义,或是干脆设计不出来,所以我们只能另辟蹊径,从另一个角度来设计状态。

回到我们的题目大意:

求从点 \(1\) 出发的两条路径 \(d_1,d_2\),使得 \({d_1}\cup{d_2} = U\)

就可以发现其是两个人什么是无所谓的,我们主要求的是两条路径。

于是求一下所有路径,然后选两条 \(s_1,s_2\),使得 \({s_1}\cup{s_2} = U\)

求路径,且并不限制对于点的重复遍历,因此我们对原来的图是什么样的并不在意,我们可以先做一次 \(Floyd\) 的求出全源最短路来求路径。

先估一下时间复杂度是 \(\Theta({n^2}{2^n}+{n^3})\) ,空间复杂度 \(O({n2^n}+{n^2})\) ,可以过。

状态转移方程的设计。

\[\Large\sum\limits_{i=1}^{2^n}{\sum\limits_{j=1}^{n}{\sum\limits_{k=1}^{n}{dp{_{{i|2^{k-1}},k}} = Min(dp{_{{i|2^{k-1}},k}},dp_{i,j}+G_{j,k})}}} \]

\(G_{j,k}\) 表示 \(Floyd\) 数组。

\(dp_{i,j}\)\(i\) 为状态压缩的状态,若 \(i\) 的二进制表示下第 \(k\)\(1\) ,则表明节点 \(k\) 已遍历。

\(dp_{i,j}\) 表示状态 \(i\) 情况下在 \(j\) 点。

这个方程别看吓人,其实并不难,他本质就是由基础状态向其他状态扩散的过程。

值得注意的是,我们所得到的路径并不是最短路径,所以要单开一个数组(\(Path\))记录路径。

设初始状态 \(dp_{1,1}\)\(0\) ,其余状态为 \(0\)

for(int i = 1 ; i < TOT; ++i)
		for(int j = 1 ; j <= n ; ++j)
			if(i & (1 << (j-1)))//可以从i点转移。 
			{
				for(int k = 1; k <= n ; ++k)
					//if(G[j][k] <= 0x3f3f3f3f && !(i&(1<<k-1)))
						dp[i|(1<<(k-1))][k] = Min(dp[i|(1<<(k-1))][k],dp[i][j] + G[j][k]);
				Path[i] = Min(Path[i],dp[i][j]);	
			}

如何求的最短路,其实也不难,我们可以通过类似 \(Floyd\) 的方法求出真正的最短路。

for(reg int k = 1; k < TOT; ++k)
	for(reg int i = 1; i <= n ; ++i)
		Path[k] = Min(Path[k],Path[k|(1<<(i-1))]);

最后,再统计一下答案。

for(reg int i = 1; i < TOT; ++i)
		ans = Min(ans,Max(Path[i],Path[(1<<n)-1-i]));

完整代码

# include <cstdio>
# include <cstring>
# define N 18
# define reg register
# define INF 0x3f3f3f3f

inline int Read()
{
	int x = 0;char ch = getchar();
	
	while(ch < ‘0‘ || ch > ‘9‘) ch = getchar();
	
	while(ch >= ‘0‘ && ch <= ‘9‘){x = x*10 + (ch^48);ch = getchar();}
	
	return x;
}

const int M = (1 << N);

inline int ABS(const int A){return A < 0 ? -A : A;}
inline int Min(const int a,const int b){return a < b ? a : b;}
inline int Max(const int a,const int b){return a > b ? a : b;}

int n,m,G[N + 42][N + 42],dp[M + 42][20],Path[M + 42],ans = 0x3f3f3f3f,TOT;

int main()
{
	n = Read();m = Read();
	
	memset(dp,0x3f,sizeof(dp));
	memset(G,0x3f,sizeof(G));
	memset(Path,0x3f,sizeof(Path));
	
	dp[1][1] = 0;//初始化。 
	
	TOT = 1<<n;
	
	for(reg int i = 1,x,y; i <= m ; ++i)
	{
		scanf("%d%d",&x,&y);
		
		G[x][y] = G[y][x] = Read();
	}

	for(reg int k = 1; k <= n ; ++k)
		for(reg int x = 1; x <= n ; ++x)
			for(reg int y = 1; y <= n ; ++y)
				G[x][y] = Min(G[x][y],G[x][k] + G[k][y]);
	
	for(int i = 1 ; i < TOT; ++i)
		for(int j = 1 ; j <= n ; ++j)
			if(i & (1 << (j-1)))//可以从i点转移。 
			{
				for(int k = 1; k <= n ; ++k)
					//if(G[j][k] <= 0x3f3f3f3f && !(i&(1<<k-1)))
						dp[i|(1<<(k-1))][k] = Min(dp[i|(1<<(k-1))][k],dp[i][j] + G[j][k]);
				Path[i] = Min(Path[i],dp[i][j]);	
			}
			
	for(reg int k = 1; k < TOT; ++k)
		for(reg int i = 1; i <= n ; ++i)
			Path[k] = Min(Path[k],Path[k|(1<<(i-1))]);
	
	for(reg int i = 1; i < TOT; ++i)
		ans = Min(ans,Max(Path[i],Path[(1<<n)-1-i]));
	
	printf("%d",ans);
	
	return 0;
}

总结一下:

  其实这道题除了路径哪里的转换以外,其余的就是一道标准状压模板(~~但是我死在了路径上~~)。

以上是关于一道状压板子题题解By 云岁月书 2020/7/16的主要内容,如果未能解决你的问题,请参考以下文章

暑期集训第四天(6-25)题解及总结

自创模拟赛set1 题解

HDU 1074 Doing Homework 状压dp(第一道入门题)

题解SDOI2009学校食堂

斯坦纳树

LuoguP3930 SAC E#1 - 一道大水题 Knight