【思维题-基环树】 [AGC004F] Namori

【题目】
Atcoder
给定一棵所有节点都是白色的基环树,每次可以将两个同色节点反色,问最少多少次能将所有节点变成黑色。
n ≤ 1 0 5 n\leq 10^5 n105

【解题思路】

首先考虑树的情况,由于树是一个二分图,我们不妨将树进行黑白染色,那么现在相当于每次能交换相邻的黑白点,求最少次数使得所有黑点变成白点,白点变成黑点。这个问题等价于求一组黑白点匹配,其代价为两点之间的距离(相当于运送过去),最小化代价。

不妨对每个点考虑其父边的贡献。若我们将黑点看作 + 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 w1w,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+xsx+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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值