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. SpringCloud 2020.0.4 系列之Eureka

    1. 概述 老话说的好:遇见困难,首先要做的是积极的想解决办法,而不是先去泄气.抱怨或生气. 言归正传,微服务是当今非常流行的一种架构方式,其中 SpringCloud 是我们常用的一种微服务框架. ...

  2. 如何从一台OPC Server访问多个PLC

    项目中遇到如下情况: 1. 整条生产线由多个PLC分别控制,但是所有PLC在同一个局域网内.PLC采用西门子的S7-200 Smart 2. 客户希望在操作工站的电脑(跟PLC在同一个局域网内)上提供 ...

  3. MySQL:提高笔记-3

    MySQL:提高笔记-3 学完基础的语法后,进一步对 MySQL 进行学习,前几篇为: MySQL:提高笔记-1 MySQL:提高笔记-2 MySQL:提高笔记-3,本文 说明:这是根据 bilibi ...

  4. UltraSoft - Beta - Scrum Meeting 7

    Date: May 23rd, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 暂无 Liuzh 前端 编写忘记密码界面 Kkkk 前端 暂无 王fuji 前端 ...

  5. logstash的安装和简单使用

    logstash的安装和简单使用 一.安装 1.下载并解压 2.logstash 一些命令行参数 1.查看帮助信息 2.加载指定pipeline文件路径 3.检测配置文件语法是否有错误 4.热加载pi ...

  6. 攻防世界 杂项 9.a_good_idea

    题目描述: 汤姆有个好主意 解题思路: 首先按照隐写思路找了一下没找到flag,接着使用winhex打开图片,发现图片里面又包含了一张图片,然后马上改了一下后缀为zip, 解压后发现里面有:hint. ...

  7. 字符串与模式匹配算法(六):Needleman–Wunsch算法

    一.Needleman-Wunsch 算法 尼德曼-翁施算法(英语:Needleman-Wunsch Algorithm)是基于生物信息学的知识来匹配蛋白序列或者DNA序列的算法.这是将动态算法应用于 ...

  8. Flink计算pv和uv的通用方法

    PV(访问量):即Page View, 即页面浏览量或点击量,用户每次刷新即被计算一次. UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客.00:00-24:00 ...

  9. 51nod_1001 数组中和等于K的数对(二分)

    题意: 给出一个整数K和一个无序数组A,A的元素为N个互不相同的整数,找出数组A中所有和等于K的数对.例如K = 8,数组A:{-1,6,5,3,4,2,9,0,8},所有和等于8的数对包括(-1,9 ...

  10. Python 字符串的encode与decode

    python的str,unicode对象的encode和decode方法 python中的str对象其实就是"8-bit string" ,字节字符串,本质上类似java中的byt ...