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

换根 dp 好题。

为啥没人做/yiw

首先 \(n\) 为奇数时答案显然为 \(0\),证明显然。接下来我们着重探讨 \(n\) 是偶数的情况。

考虑一棵树存在完美匹配的等价证明:我们考察每一条边,如果删掉该条边后两个连通块的大小都是奇数,那么显然我们如果贪心地对两个连通块进行二分图完美匹配,如果还剩至少三个点没被匹配,那么显然原图不存在二分图完美匹配,否则我们肯定会剩下该连通块的根节点,也就是这条边的一个端点。换句话,如果原图存在二分图完美匹配,那么这样的边必然在完美匹配中。而另一方面,对于所有“删掉这条边后,形成的两个连通块的大小都是偶数”的边,显然如果原图存在完美匹配那么我们对两个连通块贪心地二分图匹配后,必然是能完全匹配完的,因此这样的边不可能出现在完美匹配中。也就是说:

Lemma. 如果一张图存在完美匹配,那么完美匹配中的边集必定是所有满足“割掉这条边后形成的两个连通块大小均为奇数”的边。

也就是说,一棵树存在完美匹配,当且仅当所有满足“割掉这条边后形成的两个连通块大小均为奇数”形成一个匹配。

考虑根据这个性质对原图的边进行染色,我们称一条边为黑边,当且仅当删掉这条边后形成的两个连通块大小均为奇数。反之称其为白边,那么考虑删掉一条边并加入一条新边后每条边的黑白类型的变化情况。我们假设新加入的边的两个端点为 \((u,v)\),那么显然你删除的边在原图上必须在 \(u\to v\) 的路径上,否则最后形成不了树。那么对于一个不在 \(u\to v\)​ 的路径上的边,割掉它后显然不包含 \(u,v\) 的这个连通块不会发生变化,因此它的变型也不会发生变化,而对于在 \(u\to v\) 路径上的边,我们假设 \(u’,v’\) 为割掉的边,那么如果这条边在 \(u\to u’\) 上,那么在加入新边前后,割掉这条边后,树所形成的两个连通块之一会扣掉 \(v\) 原来所在的连通块,因此如果割掉的这条边本身是黑边,那么 \(v\) 原来所在的连通块的大小为奇数,因此原来 \(u\to v\) 的路径上的所有边类型都会翻转,反之 \(u\to v\) 路径上所有边类型不会发生变化。

感觉这一段讲得有点悬乎,借助下图可能比较好理解(绿边为原树上的边,橙边为割掉的边,红边为加上的边,填好色的三角形为一个子树,那么对于不在 \(B\to G\)​ 路径上的边,如 \(AB\)​,操作前后形成的树中,删掉这条边之后形成的连通块之一都是绿色的子树,\(CH\) 也同理,因此边的类型不发生变化,而对于 \(B\to G\) 路径上的边,如 \(BC\),原来割掉它之后形成的连通块之一是 \(B\) 加上绿色的子树,而删掉 \(DE\) 加上 \(BG\)​ 之后形成的连通块之一是 \(B,A\) 所在的子树 \(+\) 蓝色的子树,因此如果蓝色子树大小为奇数,即割掉的边为黑边那这条边类型就会发生变化,否则这条边类型不变)

因此我们将割掉的边分为黑边和白边两种类型。如果割掉的边为白边,由于所有边颜色都不变,因此如果原图中存在完美匹配,那么随便连上一条边仍然存在完美匹配,贡献就是两个子树的 \(siz\) 之积。否则不论连上什么边都不符合条件。如果割掉的边为黑边,这种情况有点繁琐,考虑分情况讨论。首先每个点连出去的黑边个数都是奇数对吧,否则总点数就是奇数了,因此

  • 如果一个点连出去了 \(5\) 条及以上的黑边,那么由于一条路径最多破坏掉一个点相邻的两个黑边,因此答案肯定为 \(0\)。

  • 否则如果存在一个点连出去了三条黑边,那么显然我们这条路径必须破坏掉所有与连出去至少三条黑边的点,同时也不能产生新的连出去至少三条黑边的点,稍加思考即可发现这等价于:

    • 记 \(c\) 表示这条路径上相邻两条边都是黑边的对数,那么必须有 \(c\) 等于 \(\text{连出去至少三条黑边的点的个数}\),否则破坏不掉所有不合法的点
    • 不能存在相邻两条边,满足它们都是白边,否则反转之后就会得到两条相邻的黑边。
    • 路径端点的两条边必须都是黑边,否则翻转后端点相连的边变成黑边,而显然你新加入的边也是黑边,就会使端点变为不合法。

    答案就是所有符合以上三条限制的路径上黑边个数之和。我们考虑怎样求这东西。我们随便令一个连出至少三条黑边的点 \(r\) 为根,然后遍历它的三个黑边连出去的子树,记 \(cnt_v\) 表示 \(r\to v\) 路径上有多少对相邻的黑边,\(ban_v\) 表示 \(r\to v\) 路径上是否有相邻的白边,\(d_v\) 表示 \(r\to v\) 路径上黑边个数。那么如果我们求出这三个数组后,我们就可以得出一条路径 \(u\to v\) 符合条件当且仅当:

    • \(ban_u=0,ban_v=0\)
    • \(u,v\) 在 \(r\) 的不同子树内,且 \(u,v\) 所在 \(r\) 的子树的根节点与 \(r\) 相连的边都是黑边
    • \(cnt_u+cnt_v\) 等于连出去至少三条黑边的点的个数 \(-1\),至于为什么减一是因为 \(r\) 也会被破坏而我们没有统计进去。
    • \(u,v\) 与其父亲相连的边都是黑边

    这样的路径的贡献就是 \(d_u+d_v\),用类似点分治的方式统计贡献即可

  • 如果不存在一个点连出去了至少三条黑边,也即原图就存在完美匹配那么我们随便找一条黑白相间且两个端点相连的边都是黑边的路径翻转即可,贡献也是路径上黑边个数。这个可以通过换根 \(dp\) 实现,\(cnt_{u,0/1}\) 表示 \(u\) 子树内有多少条路径 \(v\to u\),满足与 \(v\) 相连的边为黑边,与 \(u\) 相连的边为白边/黑边,\(sum_{u,0/1}\) 则表示所有 路径 \(v\to u\),满足与 \(v\) 相连的边为黑边,与 \(u\) 相连的边为白边/黑边,这样的路径上黑边个数之和,换根 DP 一下即可。

复杂度线性。

题解码死我了

  1. const int MAXN=5e5;
  2. int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
  3. void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
  4. int siz[MAXN+5],fa[MAXN+5],bad[MAXN+5],ed[MAXN+5];
  5. bool isbad[MAXN+5];
  6. void dfs(int x,int f){
  7. fa[x]=f;siz[x]=1;
  8. for(int e=hd[x];e;e=nxt[e]){
  9. int y=to[e];if(y==f) continue;
  10. dfs(y,x);siz[x]+=siz[y];ed[y]=e>>1;
  11. }
  12. }
  13. namespace have{
  14. ll sum[MAXN+5][2],sum_out[MAXN+5];
  15. int cnt[MAXN+5][2],cnt_out[MAXN+5];
  16. void dfs1(int x,int f){
  17. for(int e=hd[x];e;e=nxt[e]){
  18. int y=to[e];if(y==f) continue;dfs1(y,x);
  19. if(isbad[e>>1]){
  20. sum[x][1]+=sum[y][0]+cnt[y][0]+1;
  21. cnt[x][1]+=cnt[y][0]+1;
  22. } else {
  23. sum[x][0]+=sum[y][1];
  24. cnt[x][0]+=cnt[y][1];
  25. }
  26. } //printf("%d %lld %d %lld %d\n",x,sum[x][0],cnt[x][0],sum[x][1],cnt[x][1]);
  27. }
  28. void dfs2(int x,int f){
  29. ll tmp_sum[3]={0},tmp_cnt[3]={0};
  30. for(int e=hd[x];e;e=nxt[e]){
  31. int y=to[e];
  32. if(y==f){
  33. if(isbad[e>>1]){
  34. tmp_sum[1]+=sum_out[x];
  35. tmp_cnt[1]+=cnt_out[x];
  36. } else {
  37. tmp_sum[0]+=sum_out[x];
  38. tmp_cnt[0]+=cnt_out[x];
  39. }
  40. } else {
  41. if(isbad[e>>1]){
  42. tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
  43. tmp_cnt[1]+=cnt[y][0]+1;
  44. } else {
  45. tmp_sum[0]+=sum[y][1];
  46. tmp_cnt[0]+=cnt[y][1];
  47. }
  48. }
  49. }
  50. for(int e=hd[x];e;e=nxt[e]){
  51. int y=to[e];if(y==f) continue;
  52. if(isbad[e>>1]){
  53. tmp_sum[1]-=sum[y][0]+cnt[y][0]+1;
  54. tmp_cnt[1]-=cnt[y][0]+1;
  55. sum_out[y]=tmp_sum[0]+tmp_cnt[0]+1;
  56. cnt_out[y]=tmp_cnt[0]+1;
  57. tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
  58. tmp_cnt[1]+=cnt[y][0]+1;
  59. } else {
  60. tmp_sum[0]-=sum[y][1];
  61. tmp_cnt[0]-=cnt[y][1];
  62. sum_out[y]=tmp_sum[1];
  63. cnt_out[y]=tmp_cnt[1];
  64. tmp_sum[0]+=sum[y][1];
  65. tmp_cnt[0]+=cnt[y][1];
  66. } dfs2(y,x);
  67. } //printf("%d %lld %d\n",x,sum_out[x],cnt_out[x]);
  68. }
  69. void solve(){
  70. ll res=0,ss=0;
  71. for(int i=1;i<=n;i++) if(~siz[i]&1) res+=1ll*siz[i]*(n-siz[i]);
  72. dfs1(1,0);dfs2(1,0);
  73. for(int i=1;i<=n;i++){
  74. ss+=sum[i][1];
  75. if(siz[i]&1) ss+=sum_out[i];
  76. } ss>>=1;
  77. printf("%lld\n",ss+res);
  78. }
  79. }
  80. namespace doesnt_have{
  81. bool ban[MAXN+5];int cnt[MAXN+5],siz_[MAXN+5],d[MAXN+5];
  82. vector<int> pt;ll buc[MAXN+5],bucc[MAXN+5];
  83. void dfs0(int x,int f){
  84. siz_[x]=1;
  85. for(int e=hd[x];e;e=nxt[e]) if(to[e]^f){
  86. dfs0(to[e],x);siz_[x]+=siz_[to[e]];
  87. }
  88. }
  89. void dfsclc(int x,int f,int pre){
  90. pt.pb(x);
  91. for(int e=hd[x];e;e=nxt[e]){
  92. int y=to[e];if(y==f) continue;
  93. cnt[y]=cnt[x]+(pre&&(isbad[e>>1]));
  94. ban[y]=ban[x]|(!pre&&(!isbad[e>>1]));
  95. d[y]=d[x]+(siz_[y]&1);dfsclc(y,x,isbad[e>>1]);
  96. }
  97. }
  98. void solve(){
  99. int rt=0,tot=0;ll res=0;
  100. for(int i=1;i<=n;i++) if(bad[i]>=3) rt=i,tot++;
  101. dfs0(rt,0);//printf("%d\n",rt);
  102. for(int e=hd[rt];e;e=nxt[e]){
  103. int y=to[e];
  104. if(isbad[e>>1]){
  105. pt.clear();d[y]=1;cnt[y]=0;dfsclc(y,rt,1);
  106. // for(int z:pt) printf("%d %d %d %d\n",z,cnt[z],ban[z],d[z]);
  107. for(int z:pt) if((siz_[z]&1)&&!ban[z]&&bad[z]==1) res+=buc[tot-1-cnt[z]]+1ll*bucc[tot-1-cnt[z]]*d[z];
  108. for(int z:pt) if((siz_[z]&1)&&!ban[z]&&bad[z]==1) buc[cnt[z]]+=d[z],bucc[cnt[z]]++;
  109. }
  110. } printf("%lld\n",res);
  111. }
  112. }
  113. int main(){
  114. scanf("%d",&n);if(n&1) return puts("0"),0;
  115. for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
  116. dfs(1,0);for(int i=2;i<=n;i++) if(siz[i]&1) bad[i]++,bad[fa[i]]++,isbad[ed[i]]=1;
  117. bool flg=1;
  118. for(int i=1;i<=n;i++){
  119. if(bad[i]>=5) return puts("0"),0;
  120. if(bad[i]>=3) flg=0;
  121. }
  122. if(flg) have::solve();
  123. else doesnt_have::solve();
  124. return 0;
  125. }
  126. /*
  127. 8
  128. 1 2
  129. 1 3
  130. 2 4
  131. 2 5
  132. 3 6
  133. 3 7
  134. 7 8
  135. 10
  136. 1 2
  137. 1 3
  138. 1 4
  139. 2 5
  140. 2 6
  141. 6 7
  142. 5 8
  143. 5 9
  144. 2 10
  145. 14
  146. 1 2
  147. 1 3
  148. 1 4
  149. 2 5
  150. 2 6
  151. 6 7
  152. 5 8
  153. 5 9
  154. 2 10
  155. 9 11
  156. 8 12
  157. 8 13
  158. 12 14
  159. 16
  160. 1 2
  161. 1 3
  162. 1 4
  163. 2 5
  164. 2 6
  165. 6 7
  166. 5 8
  167. 5 9
  168. 2 10
  169. 9 11
  170. 8 12
  171. 8 13
  172. 12 14
  173. 13 15
  174. 13 16
  175. 10
  176. 1 2
  177. 3 2
  178. 3 4
  179. 4 5
  180. 5 6
  181. 6 7
  182. 7 8
  183. 7 9
  184. 6 10
  185. */

Codeforces 891D - Sloth(换根 dp)的更多相关文章

  1. Codeforces 997D - Cycles in product(换根 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 一种换根 dp 的做法. 首先碰到这类题目,我们很明显不能真的把图 \(G\) 建出来,因此我们需要观察一下图 \(G\) 有哪些性质.很 ...

  2. [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]

    题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...

  3. 2018.10.15 NOIP训练 水流成河(换根dp)

    传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...

  4. 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市

    P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...

  5. 小奇的仓库:换根dp

    一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...

  6. 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)

    题意 ​ 题目链接:https://www.luogu.org/problem/P4827 ​ 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...

  7. Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)

    题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...

  8. bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp

    题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...

  9. codeforces1156D 0-1-Tree 换根dp

    题目传送门 题意: 给定一棵n个点的边权为0或1的树,一条合法的路径(x,y)(x≠y)满足,从x走到y,一旦经过边权为1的边,就不能再经过边权为0的边,求有多少边满足条件? 思路: 首先,这道题也可 ...

随机推荐

  1. JVM:内存结构

    JVM:内存结构 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 内容 程序计数器 虚拟机栈 本地方法栈 堆 方法区 直接内存 1. 程序计数器 1.1 定义 P ...

  2. [对对子队]Beta阶段项目展示博客

    Beta阶段项目展示博客 1 团队成员的简介和个人博客地址 成员 头像 岗位 博客 个人介绍 黄贤昊 PM 17373253 喜欢玩游戏和做游戏,项目经验基本都和游戏相关,擅长摸鱼,偶尔敬业. 吴桐雨 ...

  3. [对对子队]会议记录4.17(Scrum Meeting8)

    今天已完成的工作 何瑞 ​ 工作内容:修复了一些bug,优化了UI ​ 相关issue:搭建关卡1 ​ 相关签入:4.17签入1 吴昭邦 ​ 工作内容:做了一些流水线系统的错误处理,添加了合成失败了之 ...

  4. 请问为什么要用三极管驱动mos,直接用mos有什么缺点呢?

    可能无法完全导通,电流可能过小使导通所需时间变长,最终导致发热严重       回复 举报     csaaa DIY七级 3# 发表于 2016-7-12 14:11:59 直接驱动mos也没什么问 ...

  5. 修炼Servlet

    修炼Servlet 一.Servlet简单认识 1.Servlet是什么 Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的 ...

  6. bash执行顺序:alias --> function --> builtin --> program

    linux bash的执行顺序如下所示: 先 alias --> function --> builtin --> program 后 验证过程: 1,在bash shell中有内置 ...

  7. uvm_cookbook--DUT-Testbench Connections--Abstract-Concrete Class Connections

    抽象和具体class的连接 An alternative to using a virtual interface handle for DUT to UVM testbench connection ...

  8. advanced base-scripting guide in chinese(高级Bash脚本编程指南-10)

    <高级Bash脚本编程指南>Revision 10中文版 github上链接地址: https://github.com/LinuxStory/Advanced-Bash-Scriptin ...

  9. aardio 开发桌面应用,这几点必须要掌握!

    1. 前言 大家好,我是安果! 上一篇文章写到可以通过 aardio 结合 Python 开发桌面应用,有些小伙伴后台给我留言,说 Aardio 资料太少,希望我能补充一些实用的功能 实用 | 利用 ...

  10. Docker 部署前后端项目

    Docker 部署前后端项目 平生不会相思,才会相思,便害相思. 简介:都是被逼的,从零开始一个Docker 部署九个微服务和三个前端项目.其中,这些服务需要用到Nacos.MySQL.Nginx.E ...