树形dp
P3174 [HAOI2009] 毛毛虫 (树的直径变式)
题目
对于一棵
求点数最多的毛毛虫
题解
本题与树的直径的求法非常类似
设
那么转移就是
其中
然后对于每个点取最大值和次大值进行一个拼接,计算答案即可
时间复杂度
[BZOJ 3743] Kamp (多次dfs多种信息的处理)
题目
一颗树
有
聚会结束后需要一辆车从举行聚会的这点出发,把这
请你回答,对于
题解
我们找出包含那
- 如果举行聚会的位置在联通块内,那么我们发现答案就是这个联通块的边权和乘
并减去联通块的直径 - 如果举行聚会的位置在连通块外,那么我们发现答案就时这个
因此考虑树形
记
我们可以一次
通过三四次
注意,我们如果求出了关键点生成树的直径的两个端点,设根据求直径时的过程,我们知道,对于在生成树上的点,生成树中距离它最远的点一定是其到直径两个端点的最大值
然后
所以总计四次(或三次)
P1131 [ZJOI2007] 时态同步 (普通树形dp)
题解
由于时间只加不减,对于一个点
BZOJ 4033 树上染色 (树形背包)
题目
有一棵点数为
问收益最大值是多少。
题解
经典题,树形背包
设
考虑转移,我们发现,对于一个结点
我们对于一个子树
那么子树外的点如何计算呢?我们不妨考虑一个经典套路,考虑每一条边的贡献
对于一条边
所以转移就可以这么写
感性理解是每一对点都只会在它们的
严格证明需要势能分析,不展开了
[hdu 6854] Kcats (笛卡尔树dp)
题目
定义维护单调栈的过程为:每次将栈头大于当前数的数弹栈,直到不能弹为止,然后加入当前数。
给一个长度为
题解
我们考虑如果没有
考虑笛卡尔树的构建过程,我们每次选择当前区间
我们向左右两边填数,这就是一个组合数问题,显然有
然后我们考虑加上
设
那么我们枚举这个区间的笛卡尔树根
所以合着这实际是道区间
[CEOI2007] 树的匹配 Treasury (树上父子关系的讨论)
题目
给一棵
题解
设
其中
剩下两个麻烦一些,令
时间复杂度
关于匹配的另一个小trick
最大匹配唯一其实等价于图中的一个点要么孤立,要么属于最大匹配
P4630 [APIO2018] 铁人两项 (圆方树上dp)
题解
考虑到当我们固定
进一步考虑,
所以建出原图的圆方树,将方点的权值设为这个点双中圆点的个数,将圆点的权值设为
我们设
- 点
是圆点时,假设它有 棵子树,它的子树对答案的贡献是
看上去这个转移是
所以我们每次记一个前缀子树和,然后乘上下一个子树的大小进行转移,这是
- 当
是方点,如果 不在这个点双内,那么其贡献一定被这个点双中的圆点中就已经统计过了,所以考虑如何计算 在点双内的方案数
- 如果
中只有一个在点双中,那么 可以选的位置就有 个,总共有 次贡献,但是当 选在了割点处,那么 就无处可选了,乘的另外一部分是相同的,所以整个就少选了 次,所以总次数就变成了 - 如果
都在点双内部,显然可以选择的位置就有 个
所以在上面圆点的转移乘上
HDU 5593 ZYB's Tree (换根dp)
题目
给定一个
对于每个点,求出离这个点距离不超过
题解
所谓换根dp,就是先一遍
对于这一道题,首先,一个点的答案显然可以分为子树内的点和子树外的点,于是我们设
对于
对于
因为
答案直接将同一个点的
P4381 [IOI2008] Island (基环树dp+单调队列优化dp)
题目
给定一个
题解
对于这种基环树的题,先将环和树分开考虑
对于每一棵基环树上的树求出它的直径,并求出它上面的点到环的最远距离为,记它于环的那个公共点为
HDU 3586 Information Disturbing (二分+树形dp)
题目
给定一个
现在要求切除其中的一些边,使得叶节点与根节点是不连通的
要求切除的总边权不可以超过
题解
首先看到最大值最小,而且答案看上去是关于最小总花费单调的,所以我们考虑进行一个二分
二分一个值为
那么对于边
- 断开边
花费 ,即 - 在
中就把所有叶子断开了,即
大小超过
CF 960E Alternating Tree (拆贡献经典套路)
题目
给定一棵带点权的树,求所有有向路径的权值和。
一条有向路径的权值如下定义:
设这条路径依次经过的点的权值分别为
则路径的权值为
题解
首先,偶数长度的路径对答案是没有任何贡献的,因为这条路径的反向路径一定存在且会抵消掉这条路径的贡献
然后直接计算是困难的,所以考虑计算每个点对答案的贡献,也就是要求出每个点作为路径上的偶数位置和奇数位置多少次,
设
设
时间复杂度
POJ 2486 Apple Tree (树上路径问题1)
题目
给定一棵
题解
设
转移分三种情况转移,注意边
- 从
到其子树中再回到
- 从
返回到 ,再到其子树
- 直接从
到其子树
转移即可
P7163 [COCI2020-2021#2] Svjetlo (树上路径问题2)
题解
难以使用语言描述的题,与 "POJ 2486 Apple Tree" 不同的是这道题没有规定起点,所以本题关键思路在于设
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,inf=0x3f;
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
int n,rt,f[N][2],g[N][2],h[N][2];
//表示走完子树之后,u是1开还是0关
//f是两头出,g是一头出,h是两头都在子树内
string c;
int cnt,head[N];
struct Edge{
int v,nxt;
}edge[N<<1];
inline void add_edge(int u,int v)
{
edge[++cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
bool ok[N];
inline void init(int u,int fa)
{
if(c[u-1]=='1') ok[u]=1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
init(v,u);
ok[u]&=ok[v];
}
return;
}
inline int Min(int x,int y,int z)
{
return min(x,min(y,z));
}
inline void dfs(int u,int fa)
{
f[u][c[u-1]-'0']=0;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa||ok[v]) continue;
dfs(v,u);
int f0=f[u][0],f1=f[u][1],g0=g[u][0],g1=g[u][1],h0=h[u][0],h1=h[u][1];
//u,v均开灯 u,v均关灯
f[u][1]=min(f0+f[v][0]+2,f1+f[v][1]+4);
//u关v开 u开v关
f[u][0]=min(f1+f[v][0]+2,f0+f[v][1]+4);
//u,v均开灯 u,v均关灯
g[u][1]=min(g0+f[v][0]+2,Min(g1+f[v][1]+4,f0+g[v][1]+1,f1+g[v][0]+3));
//u开v关 u关v开
g[u][0]=min(g1+f[v][0]+2,Min(g0+f[v][1]+4,f1+g[v][1]+1,f0+g[v][0]+3));
//u,v均开灯 u,v均关灯
h[u][1]=Min(min(f0+h[v][0]+2,f1+h[v][1]+4),min(g0+g[v][0]+2,g1+g[v][1]),min(h0+f[v][0]+2,h1+f[v][1]+4));
//u关v开 u开v关
h[u][0]=Min(min(f1+h[v][0]+2,f0+h[v][1]+4),min(g1+g[v][0]+2,g0+g[v][1]),min(h1+f[v][0]+2,h0+f[v][1]+4));
}
//当x为根时
g[u][0]=min(g[u][0],f[u][1]+1);
g[u][1]=min(g[u][1],f[u][0]+1);
h[u][0]=min(h[u][0],g[u][0]);
h[u][1]=min(h[u][1],g[u][1]);
//cout<<u<<" "<<f[u][1]<<" "<<f[u][0]<<endl;
return;
}
signed main()
{
n=read();
cin>>c;
for(int i=1;i<n;++i)
{
int u=read(),v=read();
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;++i) if(c[i-1]=='0') rt=i;
init(rt,0);
memset(f,0x3f,sizeof(f));
memset(g,0x3f,sizeof(g));
memset(h,0x3f,sizeof(h));
dfs(rt,0);
printf("%lld\n",h[rt][1]);
return 0;
}
ZR2023 NOIP 10连测 Day5 T2 (树上背包变式+二项式定理权值钦定)
题意
给定一个
对于一个
你需要求出所有排列的权值之和。答案对
题解
首先,我们发现直接去记每个点是否是到根路径上的最小点是困难的,所以我们考虑容斥原理,松弛一下条件,钦定
利用二项式定理或者GF可以解出
然后我们发现,如果以钦定点建立一棵虚树,虚树的lca节点之间并不是单纯的拓扑序关系,所以我们还需要把
设
我们先考虑合并子树答案。设
先默认
那两个组合数就是在计算前
然后再把前缀和
接下来考虑插入
- 如果
是非钦定点,那么它只要保证不小于子树里编号最大的那个钦定点,即 ,这样 一共就有 种选择,乘上去转移即可 - 如果
是钦定点,那么就从钦定点大小为 的位置更新过来,系数即为最开始所求的
时间复杂度
CODE:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=5010,mod=998244353;
int n,m,C[N][N],f[N][N],F[N],G[N],pre[N];
struct Edge{
int v,nxt;
}edge[N<<1];
int cnt,head[N];
inline void add_edge(int u,int v)
{
edge[++cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
int siz[N];
inline void dfs(int u)
{
f[u][0]=1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
dfs(v);
F[0]=f[u][0];G[0]=f[v][0];
for(int j=1;j<=siz[u];++j) F[j]=(F[j-1]+f[u][j])%mod;
for(int j=1;j<=siz[v];++j) G[j]=(G[j-1]+f[v][j])%mod;
for(int j=0;j<=siz[u]+siz[v];++j) pre[j]=0;
for(int j=0;j<=siz[u];++j)
for(int k=0;k<=siz[v];++k)
(pre[j+k]+=1ll*F[j]*G[k]%mod*C[j+k][j]%mod*C[siz[u]+siz[v]-j-k][siz[u]-j]%mod)%=mod;
for(int j=siz[u]+siz[v];j>=1;--j) (pre[j]-=pre[j-1])%=mod,(pre[j]+=mod)%=mod;
for(int j=0;j<=siz[u]+siz[v];++j) f[u][j]=pre[j];
siz[u]+=siz[v];
}
F[0]=0;
siz[u]++;
for(int i=1;i<=siz[u];++i) F[i]=(F[i-1]+f[u][i-1])%mod;
for(int i=0;i<siz[u];++i) f[u][i]=1ll*f[u][i]*(siz[u]-i)%mod;
for(int i=1;i<=siz[u];++i) (f[u][i]+=1ll*F[i]*(m-1)%mod)%=mod;
return;
}
inline void init()
{
for(int i=0;i<N;++i) C[i][0]=1;
for(int i=1;i<N;++i)
for(int j=1;j<=i;++j)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
int main()
{
init();
n=read();m=read();
for(int i=2;i<=n;++i) add_edge(read(),i);
dfs(1);
int ans=0;
for(int i=0;i<=n;++i) (ans+=f[1][i])%=mod;
printf("%d\n",ans);
return 0;
}
ZR2023 NOIP赛前20连测 Day11 T2 (增加状态维数+常规树形dp)
题意
给定一棵有根树,保证标号为一个合法的 dfs 序。把树的所有叶子按照编号顺序取出,设为
匹配:一个边集
题解
如果没有叶子之间的连边,那我们直接记
为了处理叶子之间连成的环,我们给 dp 加两个状态,设
考虑
统计答案时,对于
由于题目保证了读入顺序一定满足
时间复杂度
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=1e5+10,mod=998244353;
int n;
int siz[N];
int f[N][2][2][2],g[2][2][2],ok[N];
vector<int> G[N];
inline void dfs(int u)
{
if(!G[u].size())
{
f[u][0][0][0]=1;
siz[u]=1;
ok[u]=1;
return;
}
siz[u]=0;
for(int v:G[u])
{
dfs(v);
if(!siz[u])
{
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
{
(f[u][0][j][k]+=f[v][i][j][k])%=mod;
if(ok[v]&&!i) (f[u][1][1][1]+=f[v][i][j][k])%=mod;
else if(!i) (f[u][1][j][k]+=f[v][i][j][k])%=mod;
}
}
else
{
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
g[i][j][k]=0;
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
for(int a=0;a<2;++a)
for(int b=0;b<2;++b)
for(int c=0;c<2;++c)
{
(g[a][b][k]+=f[u][a][b][c]*f[v][i][j][k]%mod)%=mod;
if(!i&&!a) (g[1][b][ok[v]?1:k]+=f[u][a][b][c]*f[v][i][j][k]%mod)%=mod;
if(!c&&!j) (g[a][siz[u]>1?b:1][siz[v]>1?k:1]+=f[u][a][b][c]*f[v][i][j][k]%mod)%=mod;
if(!i&&!a&&!c&&!j&&!ok[v]) (g[1][siz[u]>1?b:1][siz[v]>1?k:1]+=f[u][a][b][c]*f[v][i][j][k]%mod)%=mod;
}
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
f[u][i][j][k]=g[i][j][k];
}
siz[u]+=siz[v];
}
}
signed main(){
n=read();
for(int i=2;i<=n;++i) G[read()].emplace_back(i);
dfs(1);
int ans=0;
for(int i=0;i<2;++i)
{
for(int j=0;j<2;++j)
{
for(int k=0;k<2;++k)
{
(ans+=f[1][i][j][k])%=mod;
if(!j&&!k&&siz[1]>1) (ans+=f[1][i][j][k])%=mod;
}
}
}
cout<<ans<<'\n';
return 0;
}
posted on 2023-10-27 17:59 star_road_xyz 阅读(27) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】