题解 AT2046 【[AGC004F] Namori】
unputdownable · · 题解
题目链接
AT2046 (AGC004F) Namori (这个是luogu上的链接)
luogu交不上去用这个 (这个是Vjudge上的链接)
一些思路
1.奇偶性判断
当 -1即可。
证明:
如果把每一个点每次黑白的变化叫做“一次变化”,
那么当有解是每个点必经过奇数次变化。
当
这小学生都会证我写它干吗
当然,也不是
2.注意数据范围
注意到
所以可以轻松看出这是一道基环树题。
3.转换所求内容
原题的操作为“把两个相连的同色的点的颜色转换”。
但是直接搜很难写。毕竟可能还有一对点要变1,000多次的毒瘤数据存在。
所以,需要一些技巧:
-
3-1 间隔染色 (适用于树和偶环基环树)
以样例为例:
此时每次操作相当于将两个颜色不同的点对调。
而目标状态即黑白点对调。
由此也可看出对于二分图,有解的条件就是染色后黑白点数目相等。
-
3-1-1 树
先看树,考虑每颗子树:
若有
假设
而交换了
那么将每一条边都取这个最小值,即得最小答案。
关于具体求解,令黑点为-1权值,白点为1权值,dfs跑一边求子树总权值就可以了。同时,记这个权值为
-
3-1-2偶环基环树
再看看偶环。
先剪掉一条边按树处理。
接下来考虑剪掉的这条边的贡献:
向上输送
而又有
在环上,对于这条边右边的点,
为了处理,维护数组 1和-1,就可以标记左右半环。
所以
如果把
由初中知识可得
最后注意
这个操作可以在 0实现。(不会影响中位数在的区间,
-
3-2 间隔染色-奇环基环树
一样的,先间隔染色。
发现有一条边两端的点同色(因为不是二分图)。
此时操作这条边的效果就是使黑点数量+2(全为白时)或-2(全为黑时)。
所以整张图的 n为偶数对等的)。
先预先使用这一条边操作
于是标记这条边后在dfs时进行特判,剪掉这条边。
发现剪掉以后就变成一棵树,和树一样处理就好(最后记得答案不要忘记加
code:
#include<bits/stdc++.h>
using namespace std;
// #define int long long
void read(int &x)
{
int r = 0, ne = 1;
char c;
while (c < '0' || c > '9')
{
if (c == '-')
ne = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
r = r * 10 + c - '0';
c = getchar();
}
x = r * ne;
}
struct Edge
{
/* data */
int i,nxt;
};
/* 链表 */
int h[100002],tot;
Edge e[200002];
inline void _add(int x,int y){
e[++tot].i=y;
e[tot].nxt=h[x];
h[x]=tot;
e[++tot].i=x;
e[tot].nxt=h[y];
h[y]=tot;
}
/* 输入 */
int n,m,x,y;
/* 偶环-求解 */
int s[100002],top,mid;
/* dfs */
bool odd;/* 奇环 */
int A,B;/* 环两个连接点 */
int w[100002];/* 点绿色权值(1,-1) */ /* 第二遍扫时存的是子树总权值 */
int k[100002];/* 偶环时标记多余边到LCA的点 */
int sum;/* 权值总和 */
long long ans;/* 答案 */
void dfs(int x,int fa){/* 染色 */
for(int i=h[x];i;i=e[i].nxt){
int o=e[i].i;
if(o==fa)continue;
if(w[o]){
if(w[o]==w[x])
odd=true;
A=x,B=o;
}else{
w[o]=-w[x];
// sum+=w[o];
dfs(o,x);
}
}
}
void Dfs(int x,int fa){/* 求解 */
for(int i=h[x];i!=0;i=e[i].nxt){
int o=e[i].i;
if(o==fa||(o==A&&x==B)||(o==B&&x==A))continue;/* 排除基环树的干扰 */
Dfs(o,x);
w[x]+=w[o];
k[x]+=k[o];
}
}
signed main(){
read(n),read(m);
for(register int i=0;i<m;++i){
read(x),read(y);
_add(x,y);
}
if(n%2){
puts("-1");
return 0;
}
w[1]=1;
// sum=1;
dfs(1,0);
for(int i=1;i<=n;++i)sum+=w[i];
if(m+1==n)/* 树 */{
if(sum){
puts("-1");
return 0;
}
}else{
if(odd)/* 奇环 */{
//if(sum&1){
// puts("-1");
// return 0;
//}
ans+=abs(sum>>1);
w[A]-=sum>>1,w[B]-=sum>>1;
}else/* 偶环 */{
if(sum){
puts("-1");
return 0;
}else
k[A]=1,k[B]=-1;
}
}
Dfs(1,0);
for(register int i=1;i<=n;++i){
if(k[i])
s[++top]=k[i]*w[i];
else
ans+=abs(w[i]);
}
s[++top]=0;//在栈里处理最后需要加的mid(因为偶环那一条被剪掉的边用了mid次,在后面需要用特判加。
sort(s+1,s+top+1);
mid=s[(top+1)>>1];
for(register int i=1;i<=top;++i)
ans+=abs(s[i]-mid);
printf("%lld\n",ans);
return 0;
}