【题目】
Atcoder
给定一棵所有节点都是白色的基环树,每次可以将两个同色节点反色,问最少多少次能将所有节点变成黑色。
n
≤
1
0
5
n\leq 10^5
n≤105
【解题思路】
树
首先考虑树的情况,由于树是一个二分图,我们不妨将树进行黑白染色,那么现在相当于每次能交换相邻的黑白点,求最少次数使得所有黑点变成白点,白点变成黑点。这个问题等价于求一组黑白点匹配,其代价为两点之间的距离(相当于运送过去),最小化代价。
不妨对每个点考虑其父边的贡献。若我们将黑点看作 + 1 +1 +1,白点看作 − 1 -1 −1,则父边的贡献就是子树和的绝对值。因为相当于我们在子树内就将黑白点进行匹配,一定不会更差。对所有贡献求和即可。
奇环
我们不妨先求出 dfs \text{dfs} dfs树,然后考虑环边的贡献。由于这条边破坏了二分图的性质,那么相当于我们可以使用这条边,将两个节点同时 + 1 +1 +1或 − 1 -1 −1。利用这条边的贡献次数(可以是负数,相当于同时减)我们可以直接求出来,然后将两个点的权值加上这个贡献次数后忽略这条边,套用树的方式即可。
偶环
同样先求出
dfs
\text{dfs}
dfs树,然后考虑环边的贡献。偶环的边并没有起到同层之间的交互作用,但是它可以缩短两棵外向树之间的距离。
假设环边的贡献为
w
w
w,连接的两个点为
(
u
,
v
)
(u,v)
(u,v),如果忽略掉这条环边我们可以求出每个点父边的贡献,但由于此时有环边,实际上
u
,
v
u,v
u,v的父边的贡献需要重新计算。具体来说,如果
u
u
u父边计算贡献为
w
1
w_1
w1,
v
v
v为
w
2
w_2
w2,那么其真实贡献分别为
w
1
−
w
,
w
2
+
w
w_1-w,w_2+w
w1−w,w2+w,也就是说我们通过这条环边将一些
+
1
+1
+1从
u
u
u运送到了
v
v
v,这样会使得经过
u
u
u父边的
+
1
+1
+1减少,经过
v
v
v父边的
+
1
+1
+1增多。
由于我们贡献计算的是子树和,假设
u
,
v
u,v
u,v的
lca
\text{lca}
lca为
f
f
f,那么实际上
u
u
u到
z
z
z路径上的所有父边贡献都会改变
w
w
w,而
v
v
v到
z
z
z路径上的所有父边贡献都会改变
−
w
-w
−w。
于是设原来求出的子树和为
s
i
s_i
si,我们相当于最小化一个这样的柿子:
∣
w
∣
+
∑
x
∣
s
x
+
k
x
w
∣
|w|+\sum_{x} | s_x+ k_xw |
∣w∣+x∑∣sx+kxw∣
其中
k
x
k_x
kx表示一条边父边的贡献是否会被影响,其取值只有
0
,
1
,
−
1
0,1,-1
0,1,−1三种。加上
w
w
w就是自己的贡献。
这是一个经典问题,相当于求数轴上一个点 w w w到所有点 s x s_x sx的距离和最小( w w w自己可以看作到原点距离),显然取中位数是最优的。
复杂度 O ( n ) O(n) O(n)或 O ( n log n ) O(n\log n) O(nlogn)(后者是求中位数时需要排序 233 233 233)
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],b[N];
ll sum,ans;
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(ll x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;
namespace Graph
{
int tot,ban,st,ed,odd;
int head[N],vis[N],col[N];
struct Tway{int v,nex;}e[N<<1];
void add(int u,int v)
{
e[++tot]=(Tway){v,head[u]};head[u]=tot;
e[++tot]=(Tway){u,head[v]};head[v]=tot;
}
void dfs1(int x,int f)
{
vis[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==f) continue;
if(vis[v]) {ban=i;st=x;ed=v;if(col[st]==col[ed])odd=1;}
else col[v]=col[x]^1,dfs1(v,x);
}
}
void dfs2(int x,int f)
{
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==f || i==ban || (i^1)==ban) continue;
dfs2(v,x);a[x]+=a[v];b[x]+=b[v];
}
}
}
using namespace Graph;
namespace DreamLolita
{
int n,m,cnt,num[N];
void failed(){puts("-1");exit(0);}
void solution()
{
n=read();m=read();tot=1;
for(int i=1;i<=m;++i) add(read(),read());
col[1]=1;dfs1(1,0);
for(int i=1;i<=n;++i) a[i]=col[i]?1:-1;
for(int i=1;i<=n;++i) sum+=a[i];
if(m==n-1) {if(sum) failed();}
else if(odd)
{
if(abs(sum)&1) failed();
a[st]-=sum/2;a[ed]-=sum/2;ans+=abs(sum/2);
}
else
{
if(sum) failed();
b[st]=1;b[ed]=-1;
}
dfs2(1,0);
for(int i=1;i<=n;++i)
if(!b[i]) ans+=abs(a[i]);
else num[++cnt]=-a[i];
num[++cnt]=0;
if(cnt)
{
sort(num+1,num+cnt+1);int mid=num[(cnt+1)>>1];
for(int i=1;i<=cnt;++i) ans+=abs(num[i]-mid);
}
writeln(ans);
}
}
int main()
{
DreamLolita::solution();
return 0;
}