P3748 [六省联考 2017] 摧毁“树状图”(树形dp)

Foreword \text{Foreword} Foreword

《小清新》树形 dp。
其实本题没有那么(重读)恶心,但我一开始写完 x = 0 x=0 x=0 后眼瞎没有看到 x = 1 , 2 x=1,2 x=1,2 时也必然是最优方案(这种东西不黑体吗…)可以直接无视,还在苦苦的写强制选链的特殊情况,被狠狠的恶心到了。
这个故事告诉我们,要仔细审题

感觉自己的做法还是挺舒服的,虽然核心转移看的有些长(也只有 40 行左右),但没有太多的分类讨论,和有些代码枚举儿子然后直接取十几行 max ⁡ \max max 相比感觉更加阳间。

Solution \text{Solution} Solution

题意算比较简洁清晰了,再具体一些大概就是选出两条可重点不可重边的链,将树分割成尽可能多的连通块。

(一开始我受 希望 那个题的影响,尝试了好一会用度数和点数简洁的表示出连通块数目来简化 dp,其实是在玩泥巴,直接做就很方便。)

Definition \text{Definition} Definition

设置状态 f x , i , d f_{x,i,d} fx,i,d 表示 x x x 节点子树内选择了 i i i已经确定的链,子树内 的联通块最大数目。
d d d 的定义有些复杂:
x x x 作为父亲的时候, d ∈ [ 0 , 4 ] d\in [0,4] d[0,4] 表示的是 x x x 被删除,与 x x x 相连的儿子的个数, d = 5 d=5 d=5 表示 x x x 不被删除
x x x 作为儿子的时候, d ∈ [ 0 , 1 ] d\in [0,1] d[0,1] 表示 x x x 被删除,且与父亲相连/不相连。 d = 5 d=5 d=5 表示 x x x 不被删除

Transfer \text{Transfer} Transfer

Part 1

d = 5 d=5 d=5 x x x 不删时: x x x 必然不会和儿子相连,儿子要么删了但不和父亲连( d = 0 d=0 d=0),要么根本不删 ( d = 5 d=5 d=5),不删的时候由于父子都不删,两个联通块合并成一个,所以答案要减一。
f x , i , 5 + f s o n , j , 5 − 1 → f x , i + j , 5 f_{x,i,5}+f_{son,j,5}-1\to f_{x,i+j,5} fx,i,5+fson,j,51fx,i+j,5
f x , i , 5 + f s o n , j , 0 → f x , i + j , 5 f_{x,i,5}+f_{son,j,0}\to f_{x,i+j,5} fx,i,5+fson,j,0fx,i+j,5
d ∈ [ 0 , 4 ] d\in[0,4] d[0,4] 时: x x x 可以和儿子相连也可以不连,不连的时候儿子是否删除都可以,对应转移分别就是:
f x , i , d + f s o n , j , 1 → f x , i + j , d + 1 f_{x,i,d}+f_{son,j,1}\to f_{x,i+j,d+1} fx,i,d+fson,j,1fx,i+j,d+1
f x , i , d + f s o n , j , 0 → f x , i + j , d f_{x,i,d}+f_{son,j,0}\to f_{x,i+j,d} fx,i,d+fson,j,0fx,i+j,d
f x , i , d + f s o n , j , 5 → f x , i + j , d f_{x,i,d}+f_{son,j,5}\to f_{x,i+j,d} fx,i,d+fson,j,5fx,i+j,d
这样 x x x 从所有儿子得到的转移就全完事了,接下来我们需要把 f x , i , d f_{x,i,d} fx,i,d d d d 的定义从“父亲形态”转化成“儿子形态”,以使它可以继续向父亲转移。

Part 2

d = 5 d=5 d=5 时: x x x 没选就是没选,没什么好说的。
f x , i , 5 → f x , i , 5 f_{x,i,5}\to f_{x,i,5} fx,i,5fx,i,5
d = 0 d=0 d=0 时:注意我们之前是强制让 x x x 被删除的,所以我们不能直接让它转移到 f x , i , 0 f_{x,i,0} fx,i,0。为了删除它,要么使其单独一个点成链,要么让它成为链尾和父亲相连,对应就是:
f x , i , 0 → f x , i + 1 , 0 f_{x,i,0}\to f_{x,i+1,0} fx,i,0fx,i+1,0
f x , i , 0 → f x , i , 1 f_{x,i,0}\to f_{x,i,1} fx,i,0fx,i,1

d = 2 / 4 d=2/4 d=2/4 时:向 x x x 连接的偶数条边必然两两成链:
f x , i , d → f x , i + d / 2 , 0 f_{x,i,d}\to f_{x,i+d/2,0} fx,i,dfx,i+d/2,0
d = 1 / 3 d=1/3 d=1/3 时:单出来的一条可以把 x x x 当成链头停止,也可以继续向父亲延伸:
f x , i , d → f x , i + d / 2 + 1 , 0 f_{x,i,d}\to f_{x,i+d/2+1,0} fx,i,dfx,i+d/2+1,0
f x , i , d → f x , i + d / 2 , 1 f_{x,i,d}\to f_{x,i+d/2,1} fx,i,dfx,i+d/2,1

Part 3

这样看起来就做完了?
但是你一测发现过不去样例!
仔细一看,是被两个点一条边的情况 h a c k hack hack 了。
一般的,当一个图长成深度为 1 的菊花的时候,我们都会出问题。
我们缺少了在一个点连续摆烂的情况。
这个东西有两种解决方案:
第一种是 x x x 被删除的时候,可以原地多选几条链,即:
f x , i , 0 → f x , i + 1 , 0 f_{x,i,0}\to f_{x,i+1,0} fx,i,0fx,i+1,0
f x , i , 1 → f x , i + 1 , 1 f_{x,i,1}\to f_{x,i+1,1} fx,i,1fx,i+1,1
第二种是求出度数最大点的度数,让答案和这个度数取个 max ⁡ \max max
第二种虽然看起来更加简单,跑起来也稍微快一点,但如果在考场上,本人还是建议第一种,毕竟多说多错,直接给一个合法转移,让 std::max 做决策而不是自己归纳贪心,肯定更加稳妥。
如果是做练习当然就精益求精啦。

Code \text{Code} Code

代码就简单了,把上面的转移敲出来即可。
(滚动数组无脑好用,不会互相转移出 bug,强推)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=1e5+100;
const int M=2e5+100;
const int inf=1e9;

inline ll read(){
    ll x(0),f(1);char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*f;
}

int n;
int f[N][3][6];
int tmp[2][3][6],now,pre;
vector<int>v[N];
inline void Max(int &x,int y){
    y>x?x=y:0;return;
}
void dfs(int x,int fa){
    for(int to:v[x]){
        if(to==fa||vis[to]) continue;
        dfs(to,x);
    }
    now=1;pre=0;
    memset(tmp,-0x3f,sizeof(tmp));
    tmp[now][0][5]=1;tmp[now][0][0]=0;
    for(int s:v[x]){
        if(s==fa||vis[s]) continue;
        swap(now,pre);
        memset(tmp[now],-0x3f,sizeof(tmp[now]));
        for(int i=0;i<=2;i++){
            for(int j=0;i+j<=2;j++){
                Max(tmp[now][i+j][5],tmp[pre][i][5]+f[s][j][5]-1);
                Max(tmp[now][i+j][5],tmp[pre][i][5]+f[s][j][0]);
                for(int d=0;d<=4;d++){
                    Max(tmp[now][i+j][d],tmp[pre][i][d]+f[s][j][0]);
                    Max(tmp[now][i+j][d],tmp[pre][i][d]+f[s][j][5]);
                    if(d+1<=4)Max(tmp[now][i+j][d+1],tmp[pre][i][d]+f[s][j][1]);
                }
            }   
        }
    }
    memset(f[x],-0x3f,sizeof(f[x]));
    for(int i=0;i<=2;i++){
        Max(f[x][i][5],tmp[now][i][5]);
        if(i+1<=2) Max(f[x][i+1][0],tmp[now][i][0]);
        Max(f[x][i][1],tmp[now][i][0]);
        for(int j=1;j<=4;j++){
            if(j&1){
                if(i+j/2<=2) Max(f[x][i+j/2][1],tmp[now][i][j]);
                if(i+(j+1)/2<=2) Max(f[x][i+(j+1)/2][0],tmp[now][i][j]);
            }
            else if(i+j/2<=2) Max(f[x][i+j/2][0],tmp[now][i][j]);
        }
    }
    for(int i=1;i<=2;i++){
        Max(f[x][i][0],f[x][i-1][0]);
        Max(f[x][i][1],f[x][i-1][1]);
    }
    return;
}
void work0(){
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        v[x].push_back(y);v[y].push_back(x);
    }
    dfs(1,0);
    printf("%d\n",max(f[1][2][0],f[1][2][5]));
    return;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    int T=read(),op=read();
    while(T--){
        n=read();
        for(int i=1;i<=op;i++) read(),read();
        work0();
        for(int i=1;i<=n;i++) v[i].clear();
    }
    return 0;
}
/*
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值