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 一下即可。

复杂度线性。

题解码死我了

const int MAXN=5e5;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int siz[MAXN+5],fa[MAXN+5],bad[MAXN+5],ed[MAXN+5];
bool isbad[MAXN+5];
void dfs(int x,int f){
fa[x]=f;siz[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,x);siz[x]+=siz[y];ed[y]=e>>1;
}
}
namespace have{
ll sum[MAXN+5][2],sum_out[MAXN+5];
int cnt[MAXN+5][2],cnt_out[MAXN+5];
void dfs1(int x,int f){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;dfs1(y,x);
if(isbad[e>>1]){
sum[x][1]+=sum[y][0]+cnt[y][0]+1;
cnt[x][1]+=cnt[y][0]+1;
} else {
sum[x][0]+=sum[y][1];
cnt[x][0]+=cnt[y][1];
}
} //printf("%d %lld %d %lld %d\n",x,sum[x][0],cnt[x][0],sum[x][1],cnt[x][1]);
}
void dfs2(int x,int f){
ll tmp_sum[3]={0},tmp_cnt[3]={0};
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];
if(y==f){
if(isbad[e>>1]){
tmp_sum[1]+=sum_out[x];
tmp_cnt[1]+=cnt_out[x];
} else {
tmp_sum[0]+=sum_out[x];
tmp_cnt[0]+=cnt_out[x];
}
} else {
if(isbad[e>>1]){
tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
tmp_cnt[1]+=cnt[y][0]+1;
} else {
tmp_sum[0]+=sum[y][1];
tmp_cnt[0]+=cnt[y][1];
}
}
}
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
if(isbad[e>>1]){
tmp_sum[1]-=sum[y][0]+cnt[y][0]+1;
tmp_cnt[1]-=cnt[y][0]+1;
sum_out[y]=tmp_sum[0]+tmp_cnt[0]+1;
cnt_out[y]=tmp_cnt[0]+1;
tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
tmp_cnt[1]+=cnt[y][0]+1;
} else {
tmp_sum[0]-=sum[y][1];
tmp_cnt[0]-=cnt[y][1];
sum_out[y]=tmp_sum[1];
cnt_out[y]=tmp_cnt[1];
tmp_sum[0]+=sum[y][1];
tmp_cnt[0]+=cnt[y][1];
} dfs2(y,x);
} //printf("%d %lld %d\n",x,sum_out[x],cnt_out[x]);
}
void solve(){
ll res=0,ss=0;
for(int i=1;i<=n;i++) if(~siz[i]&1) res+=1ll*siz[i]*(n-siz[i]);
dfs1(1,0);dfs2(1,0);
for(int i=1;i<=n;i++){
ss+=sum[i][1];
if(siz[i]&1) ss+=sum_out[i];
} ss>>=1;
printf("%lld\n",ss+res);
}
}
namespace doesnt_have{
bool ban[MAXN+5];int cnt[MAXN+5],siz_[MAXN+5],d[MAXN+5];
vector<int> pt;ll buc[MAXN+5],bucc[MAXN+5];
void dfs0(int x,int f){
siz_[x]=1;
for(int e=hd[x];e;e=nxt[e]) if(to[e]^f){
dfs0(to[e],x);siz_[x]+=siz_[to[e]];
}
}
void dfsclc(int x,int f,int pre){
pt.pb(x);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
cnt[y]=cnt[x]+(pre&&(isbad[e>>1]));
ban[y]=ban[x]|(!pre&&(!isbad[e>>1]));
d[y]=d[x]+(siz_[y]&1);dfsclc(y,x,isbad[e>>1]);
}
}
void solve(){
int rt=0,tot=0;ll res=0;
for(int i=1;i<=n;i++) if(bad[i]>=3) rt=i,tot++;
dfs0(rt,0);//printf("%d\n",rt);
for(int e=hd[rt];e;e=nxt[e]){
int y=to[e];
if(isbad[e>>1]){
pt.clear();d[y]=1;cnt[y]=0;dfsclc(y,rt,1);
// for(int z:pt) printf("%d %d %d %d\n",z,cnt[z],ban[z],d[z]);
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];
for(int z:pt) if((siz_[z]&1)&&!ban[z]&&bad[z]==1) buc[cnt[z]]+=d[z],bucc[cnt[z]]++;
}
} printf("%lld\n",res);
}
}
int main(){
scanf("%d",&n);if(n&1) return puts("0"),0;
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
dfs(1,0);for(int i=2;i<=n;i++) if(siz[i]&1) bad[i]++,bad[fa[i]]++,isbad[ed[i]]=1;
bool flg=1;
for(int i=1;i<=n;i++){
if(bad[i]>=5) return puts("0"),0;
if(bad[i]>=3) flg=0;
}
if(flg) have::solve();
else doesnt_have::solve();
return 0;
}
/*
8
1 2
1 3
2 4
2 5
3 6
3 7
7 8 10
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10 14
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10
9 11
8 12
8 13
12 14 16
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10
9 11
8 12
8 13
12 14
13 15
13 16 10
1 2
3 2
3 4
4 5
5 6
6 7
7 8
7 9
6 10
*/

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. [技术博客] 通过ItemTouchHelper实现侧滑删除功能

    通过ItemTouchHelper实现侧滑删除功能 一.效果 二.具体实现 demo中演示的这种左滑删除的效果在手机APP中比较常用,安卓也为我们提供了专门的辅助类ItemTouchHelper来帮助 ...

  2. 梦开始的地方(Noip模拟3) 2021.5.24

    T1 景区路线规划(期望dp/记忆化搜索) 一看题目发现肯定是概率期望题,再仔细想想这三天做的题,就知道是个期望dp. 考试思路(错): 因为聪聪与可可的10分打法根深蒂固,导致在考试时想到了用深搜( ...

  3. C++ Boost signal2信号/插槽

    #include "stdafx.h" #include "boost/signals2.hpp" #include "boost/bind.hpp& ...

  4. RocketMQ源码详解 | Broker篇 · 其一:线程模型与接收链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其二:消息组成.发送链路 中,我们终于将消息发送出了 Producer,在短暂的 tcp 握手后,很快它就会进入目的 Broker ...

  5. cat userlist(课上练习)

    问题描述 Linux文件系统的三层抽象是什么? 写出Cat userlist的过程,要详述目录文件,i-node.数据块,要画图示意. 假设块大小为4k, userlist的大小不小于10k,自己假设 ...

  6. 多线程--vthread

    vthread中包含两个类: vthread.vthread.pool vthread.vthread.thread 其中class pool的原型如下: class pool(builtins.ob ...

  7. 牛客网 剑指Offer 索引

    二维数组中的查找 替换空格 从尾到头打印链表 重建二叉树 用两个栈实现队列 旋转数组的最小数字 斐波那契数列 跳台阶 变态跳台阶 矩形覆盖 二进制中1的个数 数值的整数次方 调整数组顺序使奇数位于偶数 ...

  8. Spring事务的介绍,以及基于注解@Transactional的声明式事务

    前言 事务是一个非常重要的知识点,前面的文章已经有介绍了关于SpringAOP代理的实现过程:事务管理也是AOP的一个重要的功能. 事务的基本介绍 数据库事务特性: 原子性 一致性 隔离性 持久性 事 ...

  9. java 垃圾回收及内存分配策略

    一.在垃圾收集器对堆进行回收前,首先需要判断对象是否"存活",对已经"死去"的对象进行回收 判断对象是否存活:引用计数法和可达性分析法 引用计数法:给对象添加一 ...

  10. dart系列之:dart语言中的异常

    目录 简介 Exception和Error Throw和catch Finally 总结 简介 Exception是程序中的异常情况,在JAVA中exception有checked Exception ...