Processing math: 4%

『快乐链覆盖 树形dp』

Parsnip·2019-08-22 12:59·471 次阅读

『快乐链覆盖 树形dp』

<更新提示>

<第一次更新>


<正文>

快乐链覆盖#

Description#

给定一棵 n 个点的树,你需要找至多 k 条互不相交的路径,使得它们的长度之和最大

定义两条路径是相交的:当且仅当存在至少一个点,使得这个点在两条路径中都出现

定义一条路径的长度为该路径经过的点的数量

这个题非常简单,非常传统,但为了让它变成一道能一个顶俩的题,出题人决定让你输出任意一组方案。

Input Format#

第一行一个正整数 T 表示数据组数

接下来,对于每组数据:

第一行两个整数 n,kn,k

接下来 n−1 行,每行两个整数 a,b 描述一条树边

Output Format#

对于每组数据,第一行输出一个整数表示最大的长度之和

之后你要输出任意一组长度之和最大的方案之后你要输出任意一组长度之和最大的方案,第一行一个整数 P 表示你的方案由几条路径构成,接下来 P 行每行两个整数描述一条路径

当然,如果你只能求出答案,不能求出任意一组方案的话,本题也会给你一些部分分,具体看数据范围中的描述

但是请注意:你必须保证你的输出的格式是正确的,首先你输出的 P 必须不超过 k 且是非负整数,且无论你的方案正不正确,后面都要描述 P 条路径,例如你输出 P=2 但是只输出了一条路径的话,后面的其他组的输出可能就会被读入作为第二条路径,这会导致你得不到该有的分数

Sample Input#

Copy
1 5 2 1 2 1 3 1 4 1 5

Sample Output#

Copy
4 2 5 5 4 3

解析#

这显然是一道树形dp,我们可以先不考虑输出方案。

我们可以先设置简单的状态f[x][k]代表以x为根的子树中选了k条链的最大长度之和,但是我们发现好像不太适合从子树转移。

我们可以考虑一下从子树上转移过来会有几种情况,一个就是根节点x不包含在任何一条链中,还有就是根节点x包含在某个链中。

如果x包含在某个链中,还有两种情况:1. x现在是某个链的一端,也就是说,这条链还可以继续向上拓展。 2. x是某条链中间的一个点,也就是说有两条链在点x合并了。

那样就可以设置状态了,f[x][k][0/1/2]就分别代表了如上的三种情况。

于是我们可以考虑合并,最简单的方式就是先不考虑这棵子树影响了根节点x,把所有状态先暴力合并了,然后再考虑这棵子树向上延展,或者与其他链合并这两种影响根节点x的情况,单独转移。

这种复杂的树形dp由于没有显式地划分阶段,所以建议用滚动数组的方式转移,不建议直接转移,那样容易出现dp顺序的问题。

然后我们再考虑输出方案。一般的dp输出方案的方式都是记录每一个状态是从哪些状态转移而来的,但是显然这种背包类的dp不适合,他可能是由很多子节点共同转移过来的,直接记录空间是不够的。

但是我们考虑到一个节点顶多也就是给了他父亲一个贡献,所以我们可以换一种记录方式,把转移记录在子节点上。

记录了方案之后,我们就可以再通过一遍dfs输出了。当然,我们仍然需要对转移进行讨论,以便找到链的两个端点。

Code:

Copy
#include <bits/stdc++.h> using namespace std; const int N = 10020 , K = 520 , INF = 0x3f3f3f3f; inline int read(void) { int x = 0 , w = 0; char ch = ' '; while ( !isdigit(ch) ) w |= ch == '-' , ch = getchar(); while ( isdigit(ch) ) x = x * 10 + ch - 48 , ch = getchar(); return w ? -x : x; } int n,k,f[N][K][3],dp[K][3],size[N]; vector < pair <int,int> > ans; pair < int , int > fr[N][K][3]; vector < int > e[N]; inline void input(void) { n = read() , k = read(); for (int i=1;i<n;i++) { int x = read() , y = read(); e[x].push_back( y ); e[y].push_back( x ); } } inline bool upd(int &a,int b) { if ( b > a ) return a = b , true; return false; } inline void merge(int x,int y) { memcpy( dp , f[x] , sizeof f[x] ); for (int i=0;i<=size[x];i++) for (int j=0;j<=size[y]&&i+j<=k+1;j++) { if ( i + j <= k ) { for (int a=0;a<3;a++) for (int b=0;b<3;b++) if ( upd( dp[i+j][a] , f[x][i][a] + f[y][j][b] ) ) fr[y][i+j][a] = make_pair( j , b ); if ( j > 0 ) if ( upd( dp[i+j][1] , f[x][i][0] + f[y][j][1] + 1 ) ) fr[y][i+j][1] = make_pair( j , -1 ); } if ( i + j > 1 && j > 0 ) if ( upd( dp[i+j-1][2] , f[x][i][1] + f[y][j][1] ) ) fr[y][i+j-1][2] = make_pair( j , -1 ); } size[x] += size[y]; memcpy( f[x] , dp , sizeof dp ); } inline void dfs(int x,int fa) { for (int i=0;i<=k;i++) for (int j=0;j<3;j++) f[x][i][j] = -INF , fr[x][i][j] = make_pair(-INF,-INF); size[x] = 1 , f[x][0][0] = 0; f[x][1][1] = f[x][1][2] = 1; for ( auto y : e[x] ) if ( y != fa ) dfs( y , x ) , merge( x , y ); } inline int findpath(int x,int fa,int cnt,int op) { if ( !cnt ) return 0; reverse( e[x].begin() , e[x].end() ); int _op,cur,c,t; _op = op , cur = _op == 1 ? x : 0; if ( op < 0 ) op = 1; for ( int y : e[x] ) if ( y != fa ) { if ( cnt == 0 ) break; tie(c,t) = fr[y][cnt][op]; if ( t == -INF ) continue; if ( t == -1 ) { int ver = findpath( y , x , c , t ); if ( ver != 0 ) { if ( cur ) ans.push_back( make_pair(cur,ver) ) , cur = 0; else cur = ver; } cnt -= c - (--op); } else cnt -= c , findpath(y, x, c, t); } if ( _op > 0 && cur ) ans.push_back( make_pair(cur,cur) ) , cur = 0; else if ( _op < 1 && !cur ) return x; return cur; } inline void reset(void) { ans.clear(); for (int i=1;i<=n;i++) e[i].clear(); } int main(void) { int T; scanf("%d",&T); ans.clear(); while ( T --> 0 ) { input(); dfs( 1 , 0 ); int num = 0 , op = 0; for (int i=1;i<=k;i++) for (int j=0;j<3;j++) if ( f[1][i][j] > f[1][num][op] ) num = i , op = j; printf("%d\n",f[1][num][op]); findpath( 1 , 0 , num , op ); printf("%d\n",ans.size()); for ( auto p : ans ) { int x,y; tie( x , y ) = p; printf("%d %d\n",x,y); } reset(); } return 0; }

<后记>

posted @ 2019-08-22 12:59  Parsnip  阅读(471)  评论(0)    收藏  举报
编辑推荐:
· 领域模型应用
· 记一次 ADL 导致的 C++ 代码编译错误
· MySQL查询执行顺序:一张图看懂SQL是如何工作的
· 为什么PostgreSQL不自动缓存执行计划?
· 于是转身独立开发者
阅读排行:
· C#/.NET/.NET Core优秀项目和框架2025年6月简报
· Cursor 1.2重磅更新,这个痛点终于被解决了!
· C#开发的Panel滚动分页控件(滑动版) - 开源研究系列文章
· 上周热点回顾(6.30-7.6)
· 记一次ADL导致的C++代码编译错误
目录
点击右上角即可分享
微信分享提示