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
*/

最新文章

  1. Android编译过程中的碎碎念
  2. [我给Unity官方视频教程做中文字幕]beginner Graphics – Lessons系列之纹理Textures
  3. Java网络编程——概述
  4. Leetcode: Binary Watch
  5. [转载] EXPLAIN执行计划中要重点关注哪些要素
  6. Ant学习---第五节:Ant_Junit介绍(基于3的版本)
  7. Unity3D研究院之IOS全自动编辑framework、plist、oc代码
  8. C#连接Oracle数据库基本类
  9. 三次握手wireshark抓包分析,成功握手和失败握手
  10. 开博近一年的感想 by 程序员小白
  11. Win7如何解决telnet不是内部或外部命令的方案!听语音
  12. iOS webview 获取html中的图片地址
  13. 不错的redis文章
  14. Python - Scrapy 框架
  15. 在Ubuntu中成功搭建KMS服务器
  16. Python中的get和set方法
  17. js-signals学习以及应用
  18. 2019 wannafly winter camp
  19. object-c 混编 调用C,C++接口
  20. JAVA-JSP内置对象之pageContext对象取得不同范围属性

热门文章

  1. 【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)
  2. 如何知道当前使用的python的安装路径
  3. BOOST内存管理-intrusive_ptr
  4. 攻防世界 杂项 7.Aesop_secret
  5. 洛谷 P4587 [FJOI2016]神秘数
  6. picGo+gitee搭建Obsidian图床,实现高效写作
  7. TensorFlow从入门到入坑(2)
  8. cf14C Four Segments(计算几何)
  9. Centos 系统常用编译环境
  10. PTA 7-4 最小生成树的唯一性 (35分)