[??记录]AT2046 [AGC004F] Namori
command_block
2021-04-21 20:18:55
**题意** : 给出一张 $n$ 个点 $m$ 条边的连通图,满足 $n-1\leq m\leq n$。
初始时每个点都是白色,每次操作可以选择一条边,若两个端点的颜色相同,则可以将两个端点的颜色取反。
目标是将所有点变为黑色,求出所需的最小步数,或指出无解。
$n\leq 10^5$ ,时限$\texttt{2s}$。
------------
- $n$ 为奇数时无解
每个点最终一定参与了奇数次操作,$n$ 个奇数求和,得到奇数次对点的影响。
一次操作影响 $2$ 个点,对点的影响只可能是偶数次,矛盾。
- $n$ 为偶数,且图是一棵树
注意到树是二分图,将树黑白染色,操作能进行的条件就变为“端点异色”,且作用等价于将黑点移动一步。
目标是将黑白位置对调。
操作只能移动,不能改变黑白点的个数。由此可以发现,若黑白染色后,两种颜色的点不相同,则必然无解,否则有解。
![](https://cdn.luogu.com.cn/upload/image_hosting/byrf4cm1.png)
考虑某个子树连向父亲的边,若子树内有 $a$ 个黑点,以及 $b$ 个白点,那么该边至少要用于向内或向外运输 $|a-b|$ 个黑点。可以归纳证明这同时也是可以取到的。
具体地,将黑点的点权置为 $+1$ ,白点的点权置为 $-1$ ,记 $u$ 的子树点权和为 $c_u$ ,则答案为 $\sum\limits_{i=1}^n|c_i|$。
- $n$ 为偶数,且图是一棵基环树,环长为偶数
此时图仍然是二分图,前面的转化仍然成立。
在环上断掉一条边,看做树的情况,然后单独考虑该边的使用次数。
若按照指定方向使用了 $x$ 次,则对 $c$ 的影响如下 :
![](https://cdn.luogu.com.cn/upload/image_hosting/82geo09z.png)
将会受到影响的点的贡献写成 $\sum|w_i-x|$ 的形式,即转化为经典问题。
- $n$ 为偶数,且图是一棵基环树,环长为奇数
此时图不再是二分图了。
先断掉一条环上的边,然后黑白染色。
这条特殊的边使用规则和其他边不同,每次可以把黑黑变为白白,或者白白变为黑黑。
查看整棵树的权值和 $c_{rt}$ ,若为偶数,则可以利用这条边消除之,否则无解。
而这条边的使用次数也可以恰好为 $|c_{rt}|/2$ ,证明略去。
考虑完这条边的影响后,完全转化为树的情况。
```cpp
#include<algorithm>
#include<cstdio>
#include<vector>
#define pb push_back
#define MaxN 100500
using namespace std;
struct UFS{
int f[MaxN];
void Init(int n)
{for (int i=1;i<=n;i++)f[i]=i;}
int find(int u)
{return f[u]==u ? u : f[u]=find(f[u]);}
bool merge(int u,int v){
u=find(u);v=find(v);
if (u==v)return 0;
f[u]=v;return 1;
}
}T;
vector<int> g[MaxN];
void adl(int u,int v)
{g[u].pb(v);g[v].pb(u);}
int f[MaxN],c[MaxN];bool e[MaxN];
void dfs(int u,int fa,bool fl)
{
e[u]=fl;f[u]=fa;
if (fl)c[u]=1;else c[u]=-1;
for (int i=0;i<g[u].size();i++)
if (g[u][i]!=fa){
dfs(g[u][i],u,!fl);
c[u]+=c[g[u][i]];
}
}
int n,m,a[MaxN],ans,w[MaxN];
int lca(int u,int v)
{
for (int i=1;i<=n;i++)e[i]=0;
for (int p=u;p;p=f[p])e[p]=1;
while(!e[v])v=f[v];
return v;
}
int main()
{
scanf("%d%d",&n,&m);
if (n&1){puts("-1");return 0;}
if (m==n-1){
for (int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
adl(u,v);
}
dfs(1,0,1);
if (c[1]!=0){puts("-1");return 0;}
for (int i=1;i<=n;i++)
ans+=(c[i]<0 ? -c[i] : c[i]);
printf("%d",ans);
}else {
T.Init(n);
int u0,v0;
for (int i=1,u,v;i<=n;i++){
scanf("%d%d",&u,&v);
if (T.merge(u,v))adl(u,v);
else {u0=u;v0=v;}
}dfs(1,0,1);
if (e[u0]==e[v0]){
if (c[1]%2){puts("-1");return 0;}
int d=c[1]/2;ans+=(d<0 ? -d : d);
for (int u=u0;u;u=f[u])c[u]-=d;
for (int u=v0;u;u=f[u])c[u]-=d;
for (int i=1;i<=n;i++)
ans+=(c[i]<0 ? -c[i] : c[i]);
printf("%d",ans);
}else {
if (c[1]!=0){puts("-1");return 0;}
int tn=0,t=lca(u0,v0);
for (int u=u0;u!=t;u=f[u]){w[++tn]=c[u];c[u]=0;}
for (int u=v0;u!=t;u=f[u]){w[++tn]=-c[u];c[u]=0;}
w[++tn]=0;
for (int i=1;i<=n;i++)
ans+=(c[i]<0 ? -c[i] : c[i]);
nth_element(w+1,w+tn/2,w+tn+1);
for (int i=1;i<=tn;i++)
ans+=(w[tn/2]<=w[i] ? w[i]-w[tn/2] : w[tn/2]-w[i]);
printf("%d",ans);
}
}return 0;
}
```