[AGC004F] Namori

一、题目

点此看题

二、解法

精妙的转化,我只是想说这种东西怎么可能训练得来嘛,这完全就是靠天赋啊...

先从这种简单的情况入手,考虑间隔染色,我们把奇数深度的点染成黑色,偶数深度的点染成红色,那么问题转化成了把所有原来为黑的点变成红色,原来为红的点变成黑色,每次可以交换两个点的颜色。

考虑构造答案下界,设某个子树有 \(a\) 个黑点、\(b\) 个红点,因为最终状态是 \(b\) 个黑点、\(a\) 个红点,那么父边至少交换 \(|a-b|\) 次。又因为每次一定能找到合法的交换点对(留读者自证),所以答案下界可以取到。

那么把黑点权值设为 \(1\),红点权值设为 \(-1\),设 \(a_i\) 表示 \(i\) 子树内的权值和,答案是 \(\sum |a_i|\)

偶环树

受到树讨论的启发,考虑权值的变化即可。

img

多出来这条边的作用其实就是把权值输送到另一个子树去,设输送的权值为 \(x\),那么左边的点权值就会增加 \(x\),右边的点权值就会减少 \(x\),记 \(k_i\)\(-1/1\) 表示这个点的权值是增加还是减少,那么重新计算环上的交换次数是:

\[\sum|k_i\cdot a_i-x|+|x| \]

最小化这个式子直接取中位数即可。

奇环树

注意此种情况特殊边的作用就不是交换两个点的颜色了,而是把两个点的颜色直接反转(如果都是黑那么变成都是红、如果都是红那么变成都是黑),所以此种情况 \(a_1\) 是偶数既有解。

那么此条边使用的下界任然是 \(\frac{a_1}{2}\),还是在适当时机交换即可(读者自证),考虑这条边的影响之后当树做就行了。

三、总结

本题运用的转化:同色操作转异色操作,可以去掉同色操作的限制。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 100005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,sum,tot,f[M],a[M],w[M],st[M],A,B;ll ans;
struct edge
{
	int v,next;
}e[2*M];
int Abs(int x)
{
	return x>0?x:-x;
}
void dfs(int u,int fa)
{
	sum+=w[u];
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(w[v] && v^fa)
			A=u,B=v;
		if(!w[v])
			w[v]=-w[u],dfs(v,u);
	}
}
void cal(int u,int fa)
{
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa || (u==A && v==B) || (u==B && v==A))
			continue;
		cal(v,u);
		a[u]+=a[v];w[u]+=w[v];
	}
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	w[1]=1;dfs(1,0);
	if(sum%2) {puts("-1");return 0;}
	if(A)
	{
		if(w[A]==w[B])//odd circle
		{
			ans+=Abs(sum)/2;
			w[A]-=sum/2;w[B]-=sum/2;
			sum=0;
		}
		else
			a[A]++,a[B]--;
	}
	if(sum) {puts("-1");return 0;}
	cal(1,0);st[++k]=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]) st[++k]=a[i]*w[i];
		else if(w[i]) ans+=Abs(w[i]);
	}
	sort(st+1,st+1+k);
	int x=st[(k+1)/2];
	for(int i=1;i<=k;i++)
		ans+=Abs(st[i]-x);
	printf("%lld\n",ans);
}
posted @ 2021-09-12 10:57  C202044zxy  阅读(45)  评论(0编辑  收藏  举报