Codeforces 题面传送门 & 洛谷题面传送门

繁琐的简单树形 dp(大雾),要是现场肯定弃了去做 F 题

做了我一中午,写篇题解纪念下。

提供一种不太一样的思路。

首先碰到这样的题肯定是没法用正常的组合计数方法求解,因此我们考虑树形 \(dp\)​。显然,如果对于每条边 \((u,v)\)​ 而言,我们确定扫描到这条边时是删除 \(u\)​ 上的标记、还是删除 \(v\)​ 上的标记,还是 \(u,v\)​ 上已经有一个标记消失了(即啥也不删),并且这种钦定方式合法(即,当扫描到某条边 \(e\) 时,不会出现你钦定要删除 \(e\) 某个端点上的标记,却已经有某个端点上的标记消失了;或者你钦定啥也不删,却有 \(e\) 的两个端点上的标记都没移走),那么我们就能唯一确定最后得到的序列。

考虑从这个性质入手解决问题,一个很自然的想法是设 \(dp_x\) 表示有多少种合法的钦定 \(x\) 子树中的边的方式,不过显然这东西不太好转移,因此考虑新加一维常数维记录一些信息。注意到对于一个点 \(x\) 而言,\((x,fa_x)\) 的决策也会影响到 \(x\) 与其儿子相连的边的决策,因此考虑将 \((x,fa_x)\) 这条边也纳入 DP 状态,即 \(dp_x\) 的定义变为,有多少种合法的钦定 \(x\) 子树中的边及 \(x\) 与其父亲的边的方式。我们还能发现,当我们删除到边 \((x,fa_x)\) 时,有以下五种可能:

  • \(fa_x\) 上的标记还在,但 \(x\)​ 上的标记已经没了
  • \(x\)​​ 上的标记还在,但 \(fa_x\)​​ 上的标记已经没了
  • \(x,fa_x\) 上的标记都没了
  • \(x,fa_x\) 上的标记都在,并且我们移走了 \(x\)​ 上的标记
  • \(x,fa_x\) 上的标记都在,并且我们移走了 \(fa_x\) 上的标记

在下文中分别称这五种情况为 Condition \(0\sim\)​ Condition \(4\)​。考虑增加一维 \(dp_{x,j}\)​ 我们在强制扫描到 \(x\)​ 与其父亲的边时情况为 Condition \(j\)​ 的情况下,有多少种合法的钦定 \(x\)​ 子树内边的方案数。考虑对五种情况分别转移:

  • Condition \(0\):由于 \(x\) 上的标记已经没了,因此我们肯定会在删除某一条编号小于 \((x,fa_x)\) 且与 \(x\) 相连的边时,删除了 \(x\) 上的标记,因此我们考虑设 \(f_y\) 为有多少种钦定方案,满足我们恰好在删除 \((x,y)\) 这条边时删除了 \(x\) 的标记,那么有 \(dp_{x,0}=\sum\limits_{\text{id}(x,fa_x)>\text{id}(x,y)}f_y\),其中 \(\text{id}(x,y)\) 表示 \((x,y)\) 边的编号,\(f_y\) 的求法将在下文中讲解。

  • Condition \(1\):首先对于与 \(x\) 相连,且满足 \(\text{id}(x,y)<\text{id}(x,fa_x)\) 的边,我们肯定不能删除 \(x\) 上的标记,并且我们删除 \((x,y)\) 边也就是 \((y,fa_y)\) 时候,\(x\) 上的标记肯定还是在的,因此只有两种可能 \(dp_{y,0}\) 和 \(dp_{y,3}\),乘法原理乘起来即可,\(\text{id}(x,y)>\text{id}(x,fa_x)\) 的边,有两种选择,要么不存在某条 \((x,y)\) 删除了 \(x\) 上的标记,方案数自然也是 \(dp_{y,0}+dp_{y,3}\),要么存在,方案数就是 \(\sum\limits_{\text{id}(x,fa_x)<\text{id}(x,y)}f_y\),两部分加起来可得

    \[dp_{y,1}=\prod\limits_{(x,y)\in E}(dp_{y,0}+dp_{y,3})+\sum\limits_{\text{id}(x,fa_x)<\text{id}(x,y)}f_y
    \]
  • Condition \(2\):聪明的读者应该能发现,对于 Condition \(0\) 和 Condition \(2\) 两种情况,它们对于 \(\text{id}(x,y)<\text{id}(x,fa_x)\) 的限制相同,对于 \(\text{id}(x,y)>\text{id}(x,fa_x)\) 的限制也相同,因此 \(dp_{x,2}=dp_{x,0}\)

  • Condition \(3\)​:还是分为 \(\text{id}(x,y)<\text{id}(x,fa_x)\)​ 和 \(\text{id}(x,y)>\text{id}(x,fa_x)\)​ 两类边,\(\text{id}(x,y)<\text{id}(x,fa_x)\)​ 的边的方案数与 Condition \(1\)​ 那一部分相同,为 \(dp_{y,0}+dp_{y,3}\)​,\(\text{id}(x,y)>\text{id}(x,fa_x)\)​ 类比 Condition \(1\)​ 推理一下可知,删到 \((x,y)\)​ 时只可能是 Condition \(1,2\),因为不管怎样删到 \((x,y)\) 时 \(x\) 上的标记已经没了,因此贡献为 \(dp_{y,1}+dp_{y,2}\)​,由乘法原理可知

    \[dp_{y,3}=\prod\limits_{\text{id}(x,fa_x)>\text{id}(x,y)}(dp_{y,0}+dp_{y,3})·\prod\limits_{\text{id}(x,fa_x)<\text{id}(x,y)}(dp_{y,1}+dp_{y,2})
    \]
  • Condition \(4\):同理可得 \(dp_{x,4}=dp_{x,1}\),因为它们关于 \(\text{id}(x,y)<\text{id}(x,fa_x)\) 与 \(\text{id}(x,y)>\text{id}(x,fa_x)\) 两种情况的限制均对应相同。

接下来考虑怎么求 \(f_y\)​​,显然 \((x,y)\)​​ 的贡献为 \(dp_{y,4}\)​​,对于我们也可以将所有与 \(x\)​​ 相连且不同于 \((x,fa_x),(x,y)\)​​ 的边,我们也可以将它们分为两类:\(\text{id}(x,z)<\text{id}(x,y)\)​​ 和 \(\text{id}(x,z)>\text{id}(x,y)\)​​,仿照 Condition \(3\)​​ 的分析过程可知,第一部分的贡献为 \(dp_{z,0}+dp_{z,3}\)​,第二部分贡献为 \(dp_{z,1}+dp_{z,2}\)​,乘法原理乘起来可得:

\[f_y=dp_{y,4}·\prod\limits_{\text{id}(x,z)<\text{id}(x,y)}(dp_{z,0}+dp_{z,3})·\prod\limits_{\text{id}(x,z)>\text{id}(x,y)}(dp_{z,1}+dp_{z,2})
\]

预处理前后缀积即可 \(\mathcal O(1)\) 计算,总复杂度线性。

似乎我的这个 solution 把 condition \(0,2\)​ 和 condition \(1,4\)​ 合并之后就是 CF 官方题解给的写法?不过我这个 DP 状态至少能体现我自己的思考过程吧(

const int MAXN=2e5;
const int MOD=998244353;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1,bot[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dp[MAXN+5][5],mul[MAXN+5];
void dfs(int x,int f){
dp[x][1]=dp[x][3]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;bot[y]=e>>1;dfs(y,x);
if(bot[y]<bot[x]) dp[x][3]=1ll*dp[x][3]*(dp[y][0]+dp[y][3])%MOD;
else dp[x][3]=1ll*dp[x][3]*(dp[y][1]+dp[y][2])%MOD;
dp[x][1]=1ll*dp[x][1]*(dp[y][0]+dp[y][3])%MOD;
} vector<int> vec,pre_mul,suf_mul;
for(int e=hd[x];e;e=nxt[e]){int y=to[e];if(y==f) continue;vec.pb(y);}
reverse(vec.begin(),vec.end());pre_mul.resize(vec.size());suf_mul.resize(vec.size());
for(int i=0;i<vec.size();i++){
int y=vec[i];
pre_mul[i]=1ll*((!i)?1:pre_mul[i-1])*(dp[y][0]+dp[y][3])%MOD;
} for(int i=(int)(vec.size())-1;~i;i--){
int y=vec[i];
suf_mul[i]=1ll*((i+1==vec.size())?1:suf_mul[i+1])*(dp[y][1]+dp[y][2])%MOD;
} for(int i=0;i<vec.size();i++){
int y=vec[i];
int mul=dp[y][4];if(i) mul=1ll*mul*pre_mul[i-1]%MOD;
if(i+1<vec.size()) mul=1ll*mul*suf_mul[i+1]%MOD;
if(bot[y]<bot[x]) dp[x][0]=(dp[x][0]+mul)%MOD;
else dp[x][1]=(dp[x][1]+mul)%MOD;
} dp[x][4]=dp[x][1];dp[x][2]=dp[x][0];
}
int main(){
scanf("%d",&n);bot[1]=n;
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
dfs(1,0);printf("%d\n",(dp[1][1]+dp[1][2])%MOD);
return 0;
}

Codeforces 1276D - Tree Elimination(树形 dp)的更多相关文章

  1. Codeforces 1276D/1259G Tree Elimination (树形DP)

    题目链接 http://codeforces.com/contest/1276/problem/D 题解 我什么DP都不会做,吃枣药丸-- 设\(f_{u,j}\)表示\(u\)子树内,\(j=0\) ...

  2. Codeforces Round #263 (Div. 2) D. Appleman and Tree(树形DP)

    题目链接 D. Appleman and Tree time limit per test :2 seconds memory limit per test: 256 megabytes input ...

  3. Codeforces 804D Expected diameter of a tree(树形DP+期望)

    [题目链接] http://codeforces.com/contest/804/problem/D [题目大意] 给你一个森林,每次询问给出u,v, 从u所在连通块中随机选出一个点与v所在连通块中随 ...

  4. codeforces 161 D. Distance in Tree(树形dp)

    题目链接:http://codeforces.com/problemset/problem/161/D 题意:给出一个树,问树上点到点的距离为k的一共有几个. 一道简单的树形dp,算是一个基础题. 设 ...

  5. Codeforces Round #551 (Div. 2) D. Serval and Rooted Tree (树形dp)

    题目链接 题意:给你一个有根树,假设有k个叶子节点,你可以给每个叶子节点编个号,要求编号不重复且在1-k以内.然后根据节点的max,minmax,minmax,min信息更新节点的值,要求根节点的值最 ...

  6. Educational Codeforces Round 67 E.Tree Painting (树形dp)

    题目链接 题意:给你一棵无根树,每次你可以选择一个点从白点变成黑点(除第一个点外别的点都要和黑点相邻),变成黑点后可以获得一个权值(白点组成连通块的大小) 问怎么使权值最大 思路:首先,一但根确定了, ...

  7. HDU5834 Magic boy Bi Luo with his excited tree(树形DP)

    题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5834 Description Bi Luo is a magic boy, he also ...

  8. codeforces 709E E. Centroids(树形dp)

    题目链接: E. Centroids time limit per test 4 seconds memory limit per test 512 megabytes input standard ...

  9. BZOJ-3227 红黑树(tree) 树形DP

    个人认为比较好的(高端)树形DP,也有可能是人傻 3227: [Sdoi2008]红黑树(tree) Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1 ...

随机推荐

  1. 【Java虚拟机3】类加载器

    前言 Java虚拟机设计团队有意把类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类. ...

  2. 【数据结构与算法Python版学习笔记】图——基本概念及相关术语

    概念 图Graph是比树更为一般的结构, 也是由节点和边构成 实际上树是一种具有特殊性质的图 图可以用来表示现实世界中很多有意思的事物,包括道路系统.城市之间的航班.互联网的连接,甚至是计算机专业的一 ...

  3. [no code][scrum meeting] Beta 12

    $( "#cnblogs_post_body" ).catalog() 例会时间:5月27日11:30,主持者:乔玺华 一.工作汇报 人员 昨日完成任务 明日要完成的任务 乔玺华 ...

  4. BUAA-OO-最后单元总结

    BUAA-OO-最后单元总结 经过一学期的魔鬼"折磨"后,OO课程终于要结束了!总体来说我对于作业的总体完成情况还是比较满意的,希望最后可以取得一个理想成绩. 一.第四单元架构设计 ...

  5. 单片机零基础学习之从“点灯”入门STM32

    本篇文章通过一个简单的例子来熟悉模块化编程以及利用库函数的方法进行开发使用STM32外设的基本流程. 首先,我们打开本讲的例程,在工程目录我们可以看到驱动分组下有 led.delay 两个.c源文件, ...

  6. 攻防世界 杂项 10.2017_Dating_in_Singapore

    题目描述: 01081522291516170310172431-050607132027262728-0102030209162330-02091623020310090910172423-0201 ...

  7. 期望 概率DP

    期望 \(x\) 的期望 \(E(x)\) 表示平均情况下 \(x\) 的值. 令 \(C\) 表示常数, \(X\) 和 \(Y\) 表示两个随机变量. \(E(C)=C\) \(E(C \time ...

  8. SVN查看项目修改记录及修改内容

    工具/原料 svn 一,查看修改记录 1 选择要查看的文件夹,打开之后在空白的地方右键. 2 选择svn里面的"查看日志".show_Log 3 在弹出的日志框里,可以看到,你可以 ...

  9. 验证人员应该以何种角度阅读spec

    转载:验证人员应该以何种角度阅读spec - 微波EDA网 (mweda.com) 在开发流程中,设计和验证人员关注的点肯定是不一样的,尤其在spec的理解上,验证人员往往需要有自己独立的理解.在拿到 ...

  10. 调整数组顺序使奇数位于偶数前面 牛客网 剑指Offer

    调整数组顺序使奇数位于偶数前面 牛客网 剑指Offer 题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇 ...