<前言>
哎,前几天模拟考太多了,不是不想发博客,是想法的太多了而又没时间,还要订题啊。。。
这几天遇着两道这样的树上两两匹配问题,就是互相稍微变一下型,但我一道都没写出来。
哎,真的感觉AFO了,不过幸好的我目标不是省一。
不过有时候还是要做一些两手准备。。。(斜眼笑)
<正文>
1.MIN:
简要题意:
给出一棵n点的树,k个匹配点,求这k个点两两匹配的距离最小和。
(Solution:)
一开始以为是什么二分图的带权匹配啊啥的。
然后想了想复杂度,发现好像只能过第一个Subtest...
然后就自闭了。
结果正解就是这么个鬼东西,思想特别巧妙,加上考了两道,我决定写一写。
咳咳,不扯了。
- 1.首先我们要发现一个
显而易见的贪心结论:任何一对点,在子树内完成匹配显然是最优的。
因为我们显然可以发现若是和子树外的匹配会多出一堆没什么用的奇怪的代价。
- 2.然后,你又发现问题了:若是为每个点去寻找最优匹配,你会发现不仅时间复杂度不对,你也没什么好的算法来求解最优匹配(大佬请绕道)。
这时候,你可以考虑每条边的贡献,因为只要把每条边的贡献加起来就是总距离和了。
对于每条边,它的贡献显然是边权乘子树内无法完成匹配的点数。
因为对于匹配点,若是子树内无法完成匹配,那一定要到子树外去寻找的,这时候一定会经过此边,所以直接累加这个贡献即可。
- 3.对于一个点u,我们每次怎么合并子树信息呢?,显然直接合并多少没有匹配的即可,在一些对匹配点种类有要求的题目中可以记录每种匹配点有多少剩余。
以下的代码是一个实现两种匹配点的dfs部分。0、2一类,1、3一类。
(Code:)
void dfs(int u,int fa)
{
sp[u]=(a[u]==0||a[u]==2)?-1:1;
for(int i=fl[u];i;i=e[i].next)
if(e[i].y!=fa)
{
int v=e[i].y;
dfs(v,u);
sp[u]+=sp[v];//表示剩余匹配点个数
ans+=abs(sp[v]);//显然对于两种匹配点,只需要互相匹配即可。
}
}
2.MAX:
然后我们再考虑一题:
题意:给出一棵n点的无根树,k个匹配点,求这k个点两两匹配的距离最大和。
(Solution)
和上一题几乎一样,只需考虑改动哪些地方。
由上一题的结论得:较小子树内匹配显然不优,我们还是考虑每条边。
对于每条边,因为子树内尽量不匹配,但我们还需要考虑子树外的问题,若子树外的节点数比子树内的少,这条边最多被经过的次数不会超过子树外的匹配点数。
所以我们最后遍历一遍树,累加子树内的匹配点数,然后对于每条边使最终答案加上(min(si[v],k*2-si[v])),其中si[]数组是子树内的匹配点数。
(Code:)
void dfs(int u,int fa)
{
for(int i=fl[u];i;i=e[i].next)
if(e[i].y!=fa)
{
int v=e[i].y;
dfs(v,u);
si[u]+=si[v];//我们可以一边统计子树信息一边更新答案
}//这样一遍dfs即可完成
ans+=min(si[u],m-si[u]);
}
<后记>
emmmmm,就写到这里吧,这就一个套路,学会,记住。