P1079 盈盈一水间 官方题解
【部分分:测试点 1∼41\sim 41∼4】
- 这个时候 ,显然是很小的,但是如果硬搜索,必然过不了。
- 可以考虑直接建立完整的图,两个叶子结点间直接建立长度为 的边,跑一遍 Hamilton 回路即可。
- 用经典的状压 DP 可以求解,时间复杂度为 。
【部分分:测试点 5∼85\sim 85∼8】
-
此时所有 均为 ,只需要判断可行性。
-
一条合法路径必然从结点 出发,中途不经过结点 ,最后回到结点 。
-
对非根结点 ,路径经过 时有两种类型:从更浅的结点经过 到达叶子,或者从一个叶子经过 到达另一个叶子。
-
因此整棵树会被分成若干条不相交的路径,且每条路径都以叶子结点为端点。
-
特别地,单个叶子结点也是一条路径。
-
游览方案合法,等价于每个非叶结点在剥离出的图上度数为 ,叶子结点的度数为 或 。
-
定义 表示 分别为上述两种类型时, 的子树能否构成合法的路径。
-
若 是叶子,则 。
-
记 为 的儿子结点组成的集合。
\small
- 最后判断 是否为真。直接枚举儿子或儿子二元组,最坏时间复杂度为 。
【部分分:测试点 9∼169\sim 169∼16】
-
这部分没有 的限制,需要在判断可行性的同时最大化经过道路的总权值。
-
沿用上一档状态,把可行性改成最大价值,并令不合法状态为 。
-
表示子树内留下从 向叶子延伸的未完成路径时的最大价值。
-
表示子树内所有结点均被完整路径合法划分时的最大价值。
-
对非叶结点,初值为 。对叶子结点,有 。
-
设
- 枚举一个儿子 继续提供未完成路径,得到
- 枚举两个不同儿子 ,让两条未完成路径在 处拼成完整路径,得到
- 直接枚举儿子二元组,最坏时间复杂度为 。最终答案为 。
【部分分:测试点 17∼1917\sim 1917∼19】
-
这部分仍满足 ,但是 的范围变大。
-
统计满足 的儿子数量,记为 。这些儿子不能完整留在子树内,必须被当前结点选中。
-
同时统计满足 的儿子,它们可以向当前结点提供一条未完成路径。
-
计算 时只能选择一个儿子,因此必须有 ,并检查能否选出一个满足 的儿子。
-
计算 时需要选择两个儿子,因此必须有 ,并检查能否选出两个满足 的儿子。
-
每个结点只需要统计上述数量,不再枚举儿子二元组,时间复杂度为 。
【正解】
- 根据前面的做法,复杂度瓶颈在于枚举两个儿子。
- 实际上,每个儿子 对答案的影响只和下面这个值有关:
- 仍令
- 转移方程可以改写为
-
因此只需要维护所有儿子的 最大值和次大值。
-
若不存在足够多的合法儿子,对应状态保持为 。叶子结点仍有 。
-
最后若 仍为 ,说明不存在合法方案,输出
Pity!,否则输出 。 -
每条边只处理一次,总时间复杂度为 ,空间复杂度为 。
【参考代码】
#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 200010using namespace std; int Head[MAXN],Total=0,Fa[MAXN],In1,In2,In3,c,T,n;long long Dp[MAXN][2]; struct edge{ int St,Ed,Next; long long Value;}Edge[MAXN<<1]; void Edge_Add(int St,int Ed,int Value){ Edge[++Total]={St,Ed,Head[St],Value}; Head[St]=Total;} void dfs(int Now){ bool Leaf=1; long long Sum,Max=-1e12,SecM=-1e12; Sum=0; for(int i=Head[Now];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(To==Fa[Now]) continue; Leaf=0; Fa[To]=Now; dfs(To); Sum+=Dp[To][0]; if(Max<(Dp[To][1]+Edge[i].Value-Dp[To][0])) {SecM=Max;Max=Dp[To][1]+Edge[i].Value-Dp[To][0];} else if(Max==(Dp[To][1]+Edge[i].Value-Dp[To][0])) {SecM=Dp[To][1]+Edge[i].Value-Dp[To][0];} else {SecM=max(SecM,Dp[To][1]+Edge[i].Value-Dp[To][0]);} } Dp[Now][1]=Sum+Max; Dp[Now][0]=Sum+Max+SecM; if(Leaf){Dp[Now][0]=Dp[Now][1]=0;} return;} void Solve(){ memset(Head,0,sizeof(Head)); Total=0; Fa[1]=1; scanf("%d",&n); for(int i=1;i<=n;i++) Dp[i][0]=Dp[i][1]=-1000000009; for(int i=1;i<=n-1;i++){ scanf("%d%d%d",&In1,&In2,&In3); Edge_Add(In1,In2,In3); Edge_Add(In2,In1,In3); } dfs(1); if(Dp[1][0]>=0)printf ("%lld\n",Dp[1][0]); else puts("Pity!"); return;} int main(){ freopen("town.in","r",stdin); freopen("town.out","w",stdout); scanf("%d%d",&c,&T); while(T--) Solve(); return 0;}