uva 10308 Roads in the North
树型DP
这题刘汝佳居然归在数学题里面,他的用意应该是想归在递推的,但是这题更应该属于一个经典树DP
题意:给一个图,两个点间不会有重边,边时双向连通的,另外注意这句话,
there is only one route from a village to a village that does not pass through some other village twice.
这句话说明了,这个图是很特殊的,其实是一个无根树。要求的是,找出两点,他们的距离最远
有两种思路,但是本质还是一样的,写法不同,推荐后面那种
第一种:
/*
思考方法:这个图的本质是个无根树,所以我们指点任意一个节点为树根即可,因而指定1为树根,变为一棵树
题目变为,在这颗树中找出两个点,它们的距离最远,可知这这两个点一定都是叶子节点,因为只要一个点不是叶子节点
那么就可以继续往下或者往上延伸,距离还可以变长
那么两个叶子节点,它们的距离是整个树里面最长的,它们一定是经过某个父节点的,这个父节点,一定是整个树的树根1吗
显然不是,随便举一个例子就能证明
那么我们假设a,b两点经过的父节点为rt。其实我们很容易知道,这个rt一定是LCA(最近公共祖先)
但我们这里并不是求LCA。我们换个角度看这个问题,我们已经知道了a--->rt------>b,并且是最远距离
我们可以描述为rt----->a + rt------>b = 最远距离 , 即一个根到它两个叶子的距离之和
我们知道rt所在的子树可能有多个叶子,为什么选了a,b,肯定是因为rt到这两个叶子的距离最远
所以对于一个根,在它的子树内,找出它到所有叶子的距离,并选出最大值和次大值。
所以我们开辟两个数组d,dd,d[rt]表示rt到其子树叶子的最大值,dd[rt]表示rt到其子树叶子的次大值
另外开辟一个数组dp[rt],表示rt为根的子树内,两点间的最远距离
我们要求的实际上是dp[rt],dp[rt]有两种可能,
dp[rt]=d[rt]+dd[rt],即这两个点是经过rt的
dp[rt]=dp[son],即两个叶子节点没有经过rt,只经过了其子树的某个节点
而对于d和dd怎么求呢?
d[rt]=max{ d[son]+w } , 因为d是根到叶子的距离,所以必经过某个儿子。当可以更新d的时候,先把d给dd,
那么就记录了次大值
最后剩下建树,在这里只需要隐式建树即可,因为我们使用这棵树只要是为了遍历,遍历的话知道边的连接关系即可
可以手工模拟邻接表建树,也可以直接用vector保存
*/
注意输入:UVA的输入一贯蛋疼,case的分割就是一个空行,所以要处理掉这个空行。网上有人说,可能有坑,就是一个case里面什么都没有,用一个空行表示,然后再一个空行结束这个case,经过测试,这个坑是没有的,不必另外处理,但是我的代码里面已经做了这样的处理,都无关紧要,如果WA了,又确定自己算法没问题的话,不妨先检查一下自己的输入,可能就AC了,我就是个例子呵呵
#include <cstdio> #include <cstring> #include <vector> #include <utility> using namespace std; #define N 10100 #define INF 0x3f3f3f3f typedef pair<int,int> pii; vector<pii>a[N]; int n; int d[N],dd[N],dp[N]; bool vis[N]; void add(int u ,int v ,int w) { pii tmp; tmp.first=v; tmp.second=w; a[u].push_back(tmp); tmp.first=u; tmp.second=w; a[v].push_back(tmp); } int max(int x, int y ,int z) { int ans; ans=x>y?x:y; ans=ans>z?ans:z; return ans; } void dfs(int rt) { vis[rt]=true; pii tmp; int size=a[rt].size(); d[rt]=dd[rt]=dp[rt]=0; for(int i=0; i<size; i++) { int v,w; tmp=a[rt][i]; v=tmp.first; w=tmp.second; if(!vis[v]) { dfs(v); if(d[v]+w >= d[rt]) { dd[rt]=d[rt]; d[rt]=d[v]+w; } if(d[v]+w < d[rt] && d[v]+w > dd[rt]) dd[rt]=d[v]+w; dp[rt]=max(dp[rt] , d[rt]+dd[rt] , dp[v]); } } return ; } void solve() { memset(vis,false,sizeof(vis)); dfs(1); printf("%d\n",dp[1]); } int main() { int u,v,w; char str[50]; bool flag,End=false; while(!End) { for(int i=0; i<=N-100; i++) a[i].clear(); flag=false; while(1) { if(!gets(str)) { End=true; break;} if(!flag && str[0]=='\0') //空case,不过数据中是没有这个坑的,可以不用处理 { if(!gets(str)) End=true; break; } if(flag && str[0]=='\0') break; //一组数据的结束 flag=true; sscanf(str,"%d%d%d",&u,&v,&w); add(u,v,w); } solve(); } return 0; }
第二种:基于上面的讲解,我们不难发现,这样做是有累赘的,其实关系很简单,无非就是说
1.两个点距离最远,那么它们一定是叶子节点
2.既然它们都是叶子,那么它们一定有一个最近公共祖先(特殊除外,即图中只有两个点,它们即使叶子也可以是祖先,但是丝毫不影响解题)
3.这段距离可以描述成 rt--->a + rt----->b ,并且可知这两个值一定是最大值和次大值
4.既然这样,为什么我们不可以对每个根,都找出属于它们的rt-->a和rt--->b,然后两者相加,就可以得到以该点为中转站,得到的最远路径,然后再枚举所有rt,不就是得到了整个树中的最大值了吗?
下面的代码就是这样做的,存边部分完全一样,只是修改了solve()和dfs()。dp[rt][1]表示以rt为根到叶子的最大值,dp[rt][0]表示以rt为根到叶子的次大值
最终的答案就是 ans=max{ dp[rt][1]+dp[rt][0] }
#include <cstdio> #include <cstring> #include <vector> #include <utility> using namespace std; #define N 10100 #define INF 0x3f3f3f3f typedef pair<int,int> pii; vector<pii>a[N]; int n,ans; int dp[N][2]; bool vis[N]; void add(int u ,int v ,int w) { pii tmp; tmp.first=v; tmp.second=w; a[u].push_back(tmp); tmp.first=u; tmp.second=w; a[v].push_back(tmp); } void dfs(int rt) { vis[rt]=true; pii tmp; int v,w,size=a[rt].size(); dp[rt][1]=dp[rt][0]=0; for(int i=0; i<size; i++) { pii tmp=a[rt][i]; v=tmp.first; w=tmp.second; if(!vis[v]) { dfs(v); if(dp[v][1]+w > dp[rt][1]) { dp[rt][0] = dp[rt][1]; dp[rt][1] = dp[v][1]+w; } else if(dp[v][1]+w > dp[rt][0]) dp[rt][0] = dp[v][1]+w; } } if(dp[rt][1]+dp[rt][0] > ans) ans = dp[rt][1]+dp[rt][0]; } void solve() { memset(vis,false,sizeof(vis)); ans=0; dfs(1); printf("%d\n",ans); } int main() { int u,v,w; char str[50]; bool flag,End=false; while(!End) { for(int i=0; i<=N-100; i++) a[i].clear(); flag=false; while(1) { if(!gets(str)) { End=true; break;} if(!flag && str[0]=='\0') //空case,不过数据中是没有这个坑的,可以不用处理 { if(!gets(str)) End=true; break; } if(flag && str[0]=='\0') break; //一组数据的结束 flag=true; sscanf(str,"%d%d%d",&u,&v,&w); add(u,v,w); } solve(); } return 0; }