AGC004_F Namori
题面翻译
给你一棵树/基环树,初始所有点为白色。每次可以选择相邻的两个同色点,使它们的颜色反转(白->黑,黑->白)。问最终能否将所有点变成黑点,如能输出最少操作次数,否则输出-1。
思路
这题得分三种情况讨论……
首先最简单的是树。
我们考虑把题面转换一下。我们选一个点为根,然后所有深度为奇数/偶数的点上标+1,深度为偶数/奇数的点上标-1。每次操作将交换相邻的+1与-1。问最少移动多少次可以使每个深度为偶数/奇数的点都有棋子,而每个深度为奇数/偶数的点上都没有棋子。显然这个这个转换时没有问题的,那么如果深度为奇数的点个数等于为偶的,则可行。因为对于每次操作可以说是独立的,所以我们想让操作次数最少,使所有棋子移动距离最小,就要尽量让所有棋子在子树内移动。我们设\(S_x\)表示x的子树内深度为奇的点个数-深度为偶的点个数。如果\(S_x>0\),表示子树内总有棋子需要移出子树。如果\(S_x<0\),表示总有棋子需要移入子树内。所以答案为\(\sum_{i=1}^{n}|S_i|\)。
然后我们考虑一下环的长度为奇数的基环树。
我们考虑去掉环上的一条边,这样就变成了一棵树,但是去掉的这条边连接了两个奇偶性相同的点,这意味着多出的这条边可以在两边同时产生+1或-1。那么如果整棵树的点个数部位偶数意味着无解。同时我们只会在多出的边两端同时产生+1或-1,否则相互抵消就显得多余。那么这样是会产生还是删掉并且变化量为多少就显然了(根据整棵树奇数点个数与偶数点个数的差值)。而且每次操作相互独立,我们可以在多出的边上先进行那么多操作,再算答案。
最后是环的长度为偶数的基环树。
同样删掉一条边。但此时这条边两端奇偶相异。那么这条边的作用就如同“地下通道”,可以代替两点间每条边依次交换的操作。假设这条边两端点为x和y,lca为x和y的最近公共祖先,我们通过这条边向y运输了z个+1,那么对于x到lca(不包括lca)这一路上的点的\(S[]\)值都要-z,y到lca(不包括lca)这一路上的点的\(S[]\)值都要+z。那么当x去何值是最优呢?我们不看不会变的\(S[]\)值,那么受影响的值对答案的贡献为\(\begin{cases}|S_u-z|&,u\in x->lca\\|S_u+z|&,u\in y->lca\end{cases}\)(还要加上操作次数z)。
如果我们把y这一边的\(S[]\)值都乘上-1,那么贡献就改为\((\sum |S_u-z|(u\in x->y))+z\)。我们把\(z\)看成\(|0-z|\),然后就变成经典的库仓选址问题,直接取z=中位数即可。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1e5;
int n,m,tot=1,flag,key,cnt;
int pre[maxn*2+8],now[maxn+8],son[maxn*2+8];
int dep[maxn+8],fa[maxn+8],f[maxn+8],v[maxn+8],tmp[maxn+8];
int read()
{
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*f;
}
void add(int u,int v)
{
pre[++tot]=now[u];
now[u]=tot;
son[tot]=v;
}
void check(int x)
{
dep[x]=dep[fa[x]]+1;
v[x]=(dep[x]&1)*2-1;
f[x]=v[x];
for (int p=now[x];p;p=pre[p])
{
int child=son[p];
if (fa[x]==child) continue;
if (fa[child]) {flag=((dep[x]-dep[child]+1)&1)+1,key=p;continue;}
fa[child]=x;
check(child);
f[x]+=f[child];
}
}
void dfs(int x)
{
f[x]=v[x];
for (int p=now[x];p;p=pre[p])
{
int child=son[p];
if ((p|1)==(key|1)||fa[x]==child) continue;
dfs(child);
f[x]+=f[child];
}
}
int main()
{
n=read(),m=read();
for (int i=1;i<=m;i++)
{
int u=read(),v=read();
add(u,v),add(v,u);
}
fa[1]=1;
check(1);
if (!flag)
{
if (f[1])
{
puts("-1");
return 0;
}
ll ans=0;
for (int i=1;i<=n;i++) ans+=abs(f[i]);
printf("%lld\n",ans);
}
if (flag==2)
{
if (f[1]&1)
{
puts("-1");
return 0;
}
//for (int i=1;i<=n;i++) printf("%d ",dep[i]);puts("");
//printf("check:%d %d %d\n",f[1],son[key],son[key^1]);
//printf("%d %d\n",v[son[key]],v[son[key^1]]);
v[son[key]]-=f[1]/2;
v[son[key^1]]-=f[1]/2;
ll ans=abs(f[1]/2);
//printf("%d %d\n",v[son[key]],v[son[key^1]]);
dfs(1);
//printf("check:%d\n",f[1]);
for (int i=1;i<=n;i++) ans+=abs(f[i]);
printf("%lld\n",ans);
}
if (flag==1)
{
if (f[1])
{
puts("-1");
return 0;
}
if (dep[son[key]]<dep[son[key^1]]) key^=1;
int x=son[key];
tmp[++cnt]=0;
while(x!=son[key^1]) tmp[++cnt]=f[x],x=fa[x];
sort(tmp+1,tmp+cnt+1);
ll ans=tmp[(cnt+1)/2];
v[son[key]]-=ans;
v[son[key^1]]+=ans;
dfs(1);
ans=abs(ans);
for (int i=1;i<=n;i++) ans+=abs(f[i]);
printf("%lld\n",ans);
}
return 0;
}