【算法•日更•第十一期】信息奥赛一本通1581:旅游规划题解
废话不多说,直接上题:
1581:旅游规划
时间限制: 1000 ms 内存限制: 524288 KB
提交数: 73 通过数: 39
【题目描述】
W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。但由于人员不足,W 市市长决定只在最需要安排人员的路口安排人员。
具体来说,W 市的交通网络十分简单,由 n 个交叉路口和 n−1 条街道构成,交叉路口路口编号依次为 0,1,⋯,n−1 。任意一条街道连接两个交叉路口,且任意两个交叉路口间都存在一条路径互相连接。
经过长期调查,结果显示,如果一个交叉路口位于 W 市交通网最长路径上,那么这个路口必定拥挤不堪。所谓最长路径,定义为某条路径 p=(v1,v2,v3,⋯,vk),路径经过的路口各不相同,且城市中不存在长度大于 k 的路径,因此最长路径可能不唯一。因此 W 市市长想知道哪些路口位于城市交通网的最长路径上。
【输入】
第一行一个整数 n;
之后 n−1 行每行两个整数 u,v,表示 u 和 v 的路口间存在着一条街道。
【输出】
输出包括若干行,每行包括一个整数——某个位于最长路径上的路口编号。为了确保解唯一,请将所有最长路径上的路口编号按编号顺序由小到大依次输出。
【输入样例】
10 0 1 0 2 0 4 0 6 0 7 1 3 2 5 4 8 6 9
【输出样例】
0 1 2 3 4 5 6 8 9
【提示】
数据范围与提示:
对于全部数据,1≤n≤2×105 。
【来源】
评价一下:一道极度恶心的树(shen)型(du)动(you)态(xian)规(sou)划(suo)题。
首先先来分析题目:题目告诉有n个路口,n-1条街道,这便会使我们想到一种数据结构:树。多么巧合,树也是n个节点,n-1条边,多一条就成了图了,这就暗示了我们要使用树型动态规划。
(错误示例)而小编偏不,小编首先想到了图的最短路径算法,既然是树,那么两点间的距离不就是固定的,a到b间的最短路径不就是最长路径吗?这还不好办,一个floyed,中间顺带记录下k完事。
结果一个超时,若干答案错误。
于是小编便退求其次,看看能不能用树型动态规划,毕竟这是树型动态规划分类里的练习题,可是死活找不到状态转移方程,甚至连设计状态都不行。
哎,下下策,看别人博客,结果发现都没有用动态规划,全都是用的搜索(没有任何记忆化,所以不属于记忆化搜索),小编只想吐槽一句,一本通里的题千万不要被分类所骗,上次的加分二叉树就在这个分类,却属于区间动态规划,这道题又……
不说了,回归正题:
决定好要搜索了,那么我们就要来思考一下这个算法流程是什么样的,小编开了两个一维数组分别叫f和s。那么它们是干啥的呢?f[i]就表示以i为根的最长链,s[i]表示以i为根的次长链,你肯定要问什么是最长链?什么是次长链?先看下面的图:
忽略小编拙劣的画技,比如说1是当前的根节点,那么节点1和3就组成了一棵树,如3 -> 1 -> 0 -> 4 -> 8这条链中1 -> 3就是次长链,1 -> 0 -> 4 -> 8就是最长链,也就是说树中的一点像外延伸,可以有至多两个方向,一个方向长(最长链),一个方向短(次长链)。如果一样长就无所谓了。
了解了小编口中的最长链和次长链后,那么就来构思搜索,那么我们改放些什么参数呢?存放下当前节点u和父节点fa(上次的节点),为了干啥呢?u的延伸方向有两个,要找一个当子节点v,可别返回去把父节点当了子节点。那么在回溯过程中考虑:会有这样的情况发生:f[v]+1>f[u],那么此时说明u的最长链可以通过子节点更新,层层回溯,最长链数组f就会赋上初值。那么那么次长链s呢?在f更新的时候可以把先前f的值给s,如果f没有被更新过,那么s就是0,如果更新过,那么就一定是在其它子树中更新的,s就更新成了,子树外侧的链的长度。当上述情况不发生,而却f[v]+1>s[u]时,那么就可以更新s的值,有s[u]=f[v]+1的式子,这说明最长链已经被更新过(否则f[u]=0,一定会发生第一种情况),并且在其它子树上,所以当前子树就是次长链的一端,直接更新就好了。
但是,这还不完全对,还会出现这样的现象:①较长链的长度比最长链还长,②最长链和较长链的长度更新还不完全;因此,我们就有必要尽行第二轮的搜索,没错还要再搜一次,而第二次的重心就放在了更新树外距离上,顺便解决上述两个问题。与第一次搜索不同的是,这次搜索加入了一个新的参数:当前节点的树外最远距离dis。那么在树外会出现这样的情况:f[v]+1==f[u],很明显,这是第一次搜索时的操作,这样判断反而能告诉我们是否当前节点在u的最长链上,那么下一次的dis就要在dis+1和s[u]+1中选一个,因为在上一轮搜索中很多s已经更新过了,可以直接用,如果没有就用dis+1另起炉灶吧。同样,如果f[v]+1!=f[u],那么就在dis+1和f[u]+1选一个。
这样两次搜索之后就很清晰明了了。只要在所有总链长(即最长链f[i]+次长链s[i])中找出最长的,那么再遍历一遍把所有等于最长总链长的i输出就可以了。
最后,请注意一点:数据规模高达2*105,一定不能用二维数组,要用邻接表或其他(小编使用的是vector动态数组)。
代码如下:
1 #include<iostream> 2 #include<vector> 3 using namespace std; 4 vector<int>road[1000000]; 5 int n,a,b,maxn,f[1000000],s[1000000]; 6 void dfs1(int u,int fa) 7 { 8 for(int i=0;i<(int)road[u].size();i++) 9 { 10 int v=road[u][i]; 11 if(v==fa) continue;//子节点也连着父节点,不能往回走 12 dfs1(v,u); 13 if(f[v]+1>f[u]) //更新 14 { 15 s[u]=f[u];//把最长链先给次长链 16 f[u]=f[v]+1; 17 } 18 else if(f[v]+1>s[u]) s[u]=f[v]+1; 19 } 20 } 21 void dfs2(int u,int fa,int dis)//dis是子树外侧的链长 22 { 23 for(int i=0;i<(int)road[u].size();i++) 24 { 25 int v=road[u][i]; 26 if(v==fa) continue; 27 if(f[v]+1==f[u]) dfs2(v,u,max(dis+1,s[u]+1));//在最长链上 28 else dfs2(v,u,max(dis+1,f[u]+1));//在次长链上 29 } 30 if(dis>f[u])//更新第一次搜索后的结果 31 { 32 s[u]=f[u]; 33 f[u]=dis; 34 } 35 else if(dis>s[u]) s[u]=dis; 36 } 37 int main() 38 { 39 cin>>n; 40 for(int i=1;i<=n-1;i++) 41 { 42 cin>>a>>b; 43 road[a].push_back(b); 44 road[b].push_back(a);//路是双向的 45 } 46 dfs1(0,0);//第一次搜索 47 dfs2(0,0,0);//第二次搜索 48 for(int i=0;i<n;i++) 49 maxn=max(maxn,f[i]+s[i]);//最大总链长 50 for(int i=0;i<n;i++) 51 { 52 if(s[i]+f[i]==maxn) 53 cout<<i<<endl; 54 } 55 return 0; 56 }
这道题很不容易理解,如果真的想搞明白,那么可以使用调试,网上博客稀少,却只有几个字加代码。小编全靠调试理解的,调试是个好东西。
希望数位动态规划没有这么变态。