并不对劲的bzoj3677:p3647:[APIO2014]连珠线
题目大意
有一种生成n个点的树的方法为:
一开始有一个点,n−1次操作,每次可以有两种操作:1.选一个点,用一条红边将它与新点连接;2.将新点放在一条红边上,新点与这条红边两端点直接的连边变成蓝色
给出一个n(n≤2×105)个点的树,问如何分配边的颜色使这棵树能用上述方法生成,而且蓝边权值之和最大
题解
(想要简明且优秀的题解点这里)
考虑那种生成方法,发现每条蓝边肯定是由一条红边“分裂”的,那就可以把“连红边”和“分裂红边”放在一起考虑
将初始点当根,那么想要生成蓝边时,一定会生成一条长度为2的蓝色直链
即确定根之后,存在一种方案将所有蓝边划分成若干条长为2的直链
考虑换根dp,发现不想写
先随便找一个点当“假根”
发现在考虑点u时,根具体在哪不影响答案,但根是在 u除自己以外的子树中 还是 在u或u的子树外 会影响过点u的蓝直链形态
u是否是蓝直链的中点也会影响
那么就可以设f(u,i,j)表示考虑点u时,根在/不在u除自己以外的子树中,且u是/不是中点
又发现当根在u除自己以外的子树中,且u为中点,且过它的蓝直链在假根看来是弯链,这样它的转移就会和 过它的蓝直链在假根看来是弯链 不同
所以u就会有5个状态:f(u,0,0),f(u,0,1),f(u,1,0),f(u,1,1)(过它的直链在假根看来是弯链),f(u,1,2)(过它的直链在假根看来是直链)
多乎哉?不多也
下面考虑如何得到f(u,i,j)
发现得到f(u,1,1)需要像儿子中连2条作为过它的蓝直链的边,f(u,0,1),f(u,1,2)需要连1条作为过它的蓝直链的边,那就需要把“实际连了几条边”记在状态里
当根在u除自己以外的子树中时,还要记当前是否有根出现在儿子的子树中
所以为了求出f(u,i,j),要再给u设g(u,i,j,k),其中k表示是否已经有根出现在子树中:g(u,0,0,k),g(u,0,1,k),g(u,0,2,k),g(u,1,0,k),g(u,1,1,k),g(u,1,2,k),g(u,1,3,k),g(u,1,4,k),g(u,1,5,k)
其中:
g(u,0,0,k)同f(u,0,0)
g(u,0,1,k),g(u,0,2,k)表示想要达到f(u,0,1)的状态,实际连了0或1条作为过它的蓝直链的边
g(u,1,0,k)同f(u,1,0)
g(u,1,1,k),g(u,1,2,k),g(u,1,3,k)表示想要达到f(u,1,1)的状态,实际连了0或1或2条作为过它的蓝直链的边
g(u,1,4,k),g(u,1,5,k)表示想要达到f(u,1,2)的状态,实际连了0或1条作为过它的蓝直链的边
多乎哉?不多也
接下来考虑转移,设v为当前考虑到的u的儿子
要注意的是,v作为中点且过v的蓝直链在假根看来也是直链时,u,v之间必须为蓝边
如果u,v都不是中点,那么它们之间的边不能为红
具体转移见代码
最后的答案是max(f(假根,0,0),f(假根,1,0),f(假根,1,1)),因为假根没有父亲,而f(u,0,1),f(u,1,2)需要u与u的父亲之间连蓝边
代码
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define view(u,k) for(int k=fir[u];~k;k=nxt[k])
#define maxn 200010
#define maxm (maxn<<1)
#define inf 2147483647
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*f;
}
void write(int x)
{
if(x==0){putchar('0'),putchar('\n');return;}
int f=0;char ch[20];
if(x<0)putchar('-'),x=-x;
while(x)ch[++f]=x%10+'0',x/=10;
while(f)putchar(ch[f--]);
putchar('\n');
return;
}
int n,g[3][6][2],t[3][6][2],f[maxn][2][3],fir[maxn],v[maxm],nxt[maxm],cnt,w[maxm];
void ade(int u1,int v1,int w1){v[cnt]=v1,nxt[cnt]=fir[u1],w[cnt]=w1,fir[u1]=cnt++;}
int add(int x,int y){if(x==-inf||y==-inf)return -inf;return x+y;}
#define upd(x,y) x=max(x,y)
void getans(int u,int fa)
{
view(u,k)if(v[k]!=fa)getans(v[k],u);
rep(i,0,1)rep(j,0,5)rep(k,0,1)g[i][j][k]=-inf;
g[0][0][0]=g[0][1][0]=g[1][0][0]=g[1][1][0]=g[1][4][0]=0;
view(u,k)if(v[k]!=fa)
{
rep(i,0,2)t[0][i][0]=g[0][i][0];
rep(i,0,5)rep(l,0,1)t[1][i][l]=g[1][i][l];
//一、边(u,v)为红:
//1.根不在v子树中
rep(i,0,2)upd(g[0][i][0],add(t[0][i][0],f[v[k]][0][0]));
rep(i,0,5)rep(l,0,1)upd(g[1][i][l],add(t[1][i][l],f[v[k]][0][0]));
//2.根在v子树中
//当过u的蓝直链在假根看来是直链时,在v中的根看来一定是弯链,所以没有到g(u,1,1/2/3/4/5,0/1)的转移
upd(g[1][0][1],add(t[1][0][0],max(f[v[k]][1][1],f[v[k]][1][0])));
//二、边(u,v)为蓝
//1.根不在v子树中
rep(i,0,2)upd(g[0][i][0],add(add(t[0][i][0],f[v[k]][0][1]),w[k]));
rep(i,0,5)rep(l,0,1)upd(g[1][i][l],add(add(t[1][i][l],f[v[k]][0][1]),w[k]));
rep(l,0,1)
{
upd(g[0][2][l],add(add(t[0][1][l],f[v[k]][0][0]),w[k]));
upd(g[1][2][l],add(add(t[1][1][l],f[v[k]][0][0]),w[k]));
upd(g[1][3][l],add(add(t[1][2][l],f[v[k]][0][0]),w[k]));
upd(g[1][5][l],add(add(t[1][4][l],f[v[k]][0][0]),w[k]));
}
// 2.根在v子树中
upd(g[1][0][1],add(add(t[1][0][0],f[v[k]][1][2]),w[k]));
upd(g[1][2][1],add(add(t[1][1][0],max(f[v[k]][1][0],f[v[k]][1][1])),w[k]));
upd(g[1][3][1],add(add(t[1][2][0],max(f[v[k]][1][0],f[v[k]][1][1])),w[k]));
upd(g[1][5][1],add(add(t[1][4][0],max(f[v[k]][1][0],f[v[k]][1][1])),w[k]));
}
f[u][0][0]=g[0][0][0],f[u][0][1]=g[0][2][0],f[u][1][0]=max(0,g[1][0][1]),
//f(u,1,0)实际意义是“根在u子树中除u以外的区域时的答案” (即u没儿子时为-inf)
//但当它转移到u的父亲时,它的意义是 “根在u子树中时的答案”(即u没儿子时为0)
//而且最终答案最小值为0,所以在这里将它改为0不影响统计答案
f[u][1][1]=g[1][3][1],f[u][1][2]=g[1][5][1];
}
int main()
{
memset(fir,-1,sizeof(fir));
n=read();
rep(i,1,n-1){int x=read(),y=read(),z=read();ade(x,y,z),ade(y,x,z);}
getans(1,0);
write(max(f[1][0][0],max(f[1][1][0],f[1][1][1])));
return 0;
}
/*
5
1 2 10
1 3 40
1 4 15
1 5 20
*/
一些感想
换根dp可以记一位状态表示“真的根在不在该子树里”。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术