一道状压板子题题解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})\) ,可以过。
状态转移方程的设计。
\(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的主要内容,如果未能解决你的问题,请参考以下文章