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. Sequence Model-week1编程题1(一步步实现RNN与LSTM)

    一步步搭建循环神经网络 将在numpy中实现一个循环神经网络 Recurrent Neural Networks (RNN) are very effective for Natural Langua ...

  2. Python中Numpy及Matplotlib使用

    Python中Numpy及Matplotlib使用 1. Jupyter Notebooks 作为小白,我现在使用的python编辑器是Jupyter Notebook,非常的好用,推荐!!! 你可以 ...

  3. MVC之三个单选按钮的切换选择

    实现需求: 1.三个多选按钮中:只能同时选择限时抢购和分享金或者拼团特惠和分享金,其中限时抢购和拼团特惠不能同时选择.并且点击后显示,再次点击赢隐藏. 1 @*活动信息*@ 2 <div> ...

  4. Ubuntu Python2 和 Python3 共存 切换

    例如 你写了代码 创建一个文件 在终端 vim test.py 然后写入代码 print "hello world" 接着运行代码 python test.py 会输出 hello ...

  5. Nginx多种安装方式

    不指定参数配置的Nginx编译安装 ./configuremake make install wget下载或浏览器下载上传.解压进入目录[root@mcw1 nginx-1.10.2]# ls #查看 ...

  6. 修改openstack镜像--支持root密码登陆

    一.前言 从openstack官方下载的云镜像一般都是普通用户密钥登陆,比如centos镜像的普通用户为centos,ubuntu镜像的普通用户为ubuntu,虽然密钥登陆系统相比密码登陆来说比较方便 ...

  7. Spring Cloud Gateway实战之一:初探

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<Spring Cloud Gateway实 ...

  8. 小白都能看懂的Spring源码揭秘之IOC容器源码分析

    目录 前言 IOC 只是一个 Map 集合 IOC 三大核心接口 IOC 初始化三大步骤 定位 加载 注册 总结 前言 在 Spring 框架中,大家耳熟能详的无非就是 IOC,DI,Spring M ...

  9. Matlab画colormap的一种色彩搭配方法

    声学与振动数据分析经常需要画colormap,来识别是结构频率共振,还是激励源阶次问题,比如图1,横坐标表示电机的转速,负值表示CW(顺时针)方向转动,正值表示CCW逆时针方向转动.Y轴表示对应的声音 ...

  10. 【java + selenium3】窗口基本操作及8大定位元素方法总结(一)

    一.窗口基本操作 1. 关于窗口的设置都是由window对象提供的: 获取window的对象方法: driver.manage().window(); //1.获取 window 对象 Window ...