CF1646D Weight the Tree
传送门
题意
有一颗树,如果一个点的权值等于所有与它相邻的节点的权值和,那么这个点就是好的。
你需要为每个点赋予一个权值,使得好的点最多,如果有多种方案,你需要选出所有节点权值和最小的。
输出答案和每个点的权值。
提示
我真的降大智, 不会dp了属于是还要看提示
时,两个点都为显然最优。
时, 容易证明一条边连接的两个节点中最多只有一个是好的。
题解
看到这,我就会了,其实我也想到这两个结论了,但是神志不清的。
先考虑最多的合法点,显然对于任意一个没有相邻节点的点集,肯定是可以让这个点集中的所有点都是合法的:
只要给不在这个点集中的点随便赋值,然后让点集中的点等于相邻节点的权值和即可。
也就是说我们要求:能选出的最多的点的数量,使得两两之间不相邻。
这个东西显然可以dp,设 表示一个点选了或没选时子树的最大点数,随便转移即可。
然后考虑最小总权值和, 对于一个没选的点显然赋值 最优。对于选了的点, 他的权值等于它的度数,因为与他相邻的点都没选且都为。由于是在保证点数最多的情况下权值和最小,所以只要记录最大答案的最小权值即可,具体做法是, 转移时,选取答案最大的决策, 如果有多个,选权值最小的。
就这样了,不大难
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】