『快乐链覆盖 树形dp』

时间:2019-08-22
本文章向大家介绍『快乐链覆盖 树形dp』,主要包括『快乐链覆盖 树形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

1
5 2
1 2
1 3
1 4
1 5

Sample Output

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:\)

#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;
}

<后记>

原文地址:https://www.cnblogs.com/Parsnip/p/11393731.html