CF1646D Weight the Tree

传送门

题意

有一颗树,如果一个点的权值等于所有与它相邻的节点的权值和,那么这个点就是好的。
你需要为每个点赋予一个权值,使得好的点最多,如果有多种方案,你需要选出所有节点权值和最小的。
输出答案和每个点的权值。

\(n<2e5, 1 \leq w < 10^9\)

提示

我真的降大智, 不会dp了属于是还要看提示
\(n=2\)时,两个点都为\(1\)显然最优。
\(n>2\)时, 容易证明一条边连接的两个节点中最多只有一个是好的。

题解

看到这,我就会了,其实我也想到这两个结论了,但是神志不清的。
先考虑最多的合法点,显然对于任意一个没有相邻节点的点集,肯定是可以让这个点集中的所有点都是合法的:
只要给不在这个点集中的点随便赋值,然后让点集中的点等于相邻节点的权值和即可。

也就是说我们要求:能选出的最多的点的数量,使得两两之间不相邻。
这个东西显然可以dp,设 \(f_{i, 0/1}\) 表示一个点选了或没选时子树的最大点数,随便转移即可。

然后考虑最小总权值和, 对于一个没选的点显然赋值 \(1\) 最优。对于选了的点, 他的权值等于它的度数,因为与他相邻的点都没选且都为\(1\)。由于是在保证点数最多的情况下权值和最小,所以只要记录最大答案的最小权值即可,具体做法是, 转移时,选取答案最大的决策, 如果有多个,选权值最小的。

就这样了,不大难

Remake

最近发现反思总结是目前进步的重要方法,于是准备增加一个\(remake\)板块。
以后题解分两种:一种是教程型的,我写出来了,想法还不错,把这个做法的本质多想想,做总结,考虑适用性和本质,使其可以应用于更多的题,这个做法已经在延迟触发中得到较好效果。 第二种是反思总结型,我没做出来,或者看提示了,或者做法不过好了, 实事求是,反思总结,我为什么做不出来?要有一种自己不应该做不出来的态度面对这些问题。

反思这题: 思路乱了,性质没有总结透彻,许多cf的题,明明是同一个结论,它的表述很清晰,相比于我自己的想法而言。这中清晰的定义的性质,很大的帮助了它的转化。然后是最近降智, 没有怎么考虑dp, dp是很重要的,最近开始回归水平。

还有不能太乱,不要一开始就考虑最小权值,从子情况,子任务出发分析问题。

定义式分析是很重要的,我们要时刻清楚,这个题求的实际上是什么,这个性质实际上是什么,有了这个性质实际上就转化为巴拉巴拉,什么最大时,什么是不是也最大。这种想法有效帮助我们转化问题。

实现

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

int read(){
    int num=0, flag=1; char c=getchar();
    while(!isdigit(c) && c!='-') c=getchar();
    if(c == '-') c=getchar(), flag=-1;
    while(isdigit(c)) num=num*10+c-'0', c=getchar();
    return num*flag;
}

const int N = 290005;
int T, n;
vector<int> p[N];
int f[N][2], d[N][2], fa[N];

void dp(int x){
    f[x][1] = 1, d[x][1]= p[x].size(); d[x][0]=1;
    for(auto nex : p[x]){
        if(nex == fa[x]) continue;
        fa[nex] = x;
        dp(nex); 
        
        f[x][1] += f[nex][0];
        d[x][1] += d[nex][0];

        if(f[nex][1] == f[nex][0]) f[x][0]+=f[nex][1], d[x][0]+=min(d[nex][1], d[nex][0]);
        else if(f[nex][1] > f[nex][0]) f[x][0]+=f[nex][1], d[x][0]+=d[nex][1];
        else f[x][0]+=f[nex][0], d[x][0]+=d[nex][0];
    }
    
}

int a[N];

void build(int x, int flag){
    if(flag){
        a[x] = p[x].size();
        for(auto i : p[x]) if(i!=fa[x]) build(i, 0);
    }else{
        a[x] = 1;
        for(auto i : p[x]){
            if(i == fa[x]) continue;
            if(f[i][0] == f[i][1]){
                if(d[i][0] < d[i][1]){
                    build(i, 0);
                }else{
                    build(i, 1);
                }
            }else if(f[i][0] > f[i][1]){
                build(i, 0);
            }else{
                build(i, 1);
            }
        }
    }
}

int main(){ 
    n = read();
    
    for(int i=1; i<=n-1; i++){
        int u=read(), v=read();
        p[u].push_back(v);
        p[v].push_back(u);
    }
    if(n == 2){
        printf("%d %d\n%d %d", 2, 2, 1, 1);
        return 0;
    }
    dp(1);
    if(f[1][0] == f[1][1]){
        if(d[1][0] < d[1][1]){
            printf("%d %d\n", f[1][0], d[1][0]);
            build(1, 0);
        }else{
            printf("%d %d\n", f[1][1], d[1][1]);
            build(1, 1);
        }
    }else if(f[1][0] > f[1][1]) {
        printf("%d %d\n", f[1][0], d[1][0]);
        build(1, 0);
    }else {
        printf("%d %d\n", f[1][1], d[1][1]);
        build(1, 1);
    }
    for(int i=1; i<=n; i++) printf("%d ", a[i]);
    return 0;
}
posted @ 2022-03-09 20:25  ltdJcoder  阅读(96)  评论(0编辑  收藏  举报