[AGC004F]Namori

98 篇文章 0 订阅
89 篇文章 0 订阅

题目

传送门 to AtCoder

思路

A t C o d e r AtCoder AtCoder 是不是很喜欢玩这种重新染色的套路?

从树的情况开始做起。发现操作同色点不太好,转为操作异色点——只需要将深度为奇数的点的颜色翻转。

考虑黑点和白点交换颜色,实际上就是 白点在移动,白点没覆盖则为黑点。而目标是将所有点的颜色翻转,即原来是黑点的——下文称其为坑——将会有一个白点填进来。

两个白点之间不能操作。然而这并不代表两个白点之间不能互相跃过,因为两种情况代价相同,即 a − b − o a-b-o abo 变为 a − o − b a-o-b aob 再变为 o − a − b o-a-b oab 是两步,其中 a , b a,b a,b 是白色球;而 a − b − o a-b-o abo 直接走到 o − b − a o-b-a oba 也是两步,效果相同。

所以问题变为匹配白点和坑,使得走过的总距离最小。可以计算每一条边的贡献,必须经过这条边的点即某一边点的数量与坑的数量差的绝对值。

这样的方案肯定可以构造出来——找到一个白点,其旁边必然有一条边有它提供的贡献,走过去即可。

考虑基环树。如果环是一个偶环,那么黑白染色仍然可行,也就是说,现在白点可以在一个环上移动了。而树的部分的最优操作显然仍然与上面相同,进行完这些操作,相当于根节点(环上点)有若干个白点,或者缺少若干个白点。

原来这就是 传递糖果。 即,设一号点从 k k k 号点得到了 x x x 个点(假定 x x x 可以为负,表示给出),那么一号点就会给出 x + c 1 x+c_1 x+c1,这里 c c c 为原本拥有的白点(可以为负,表示需要),同理二号点需要给出 x + c 1 + c 2 x+c_1+c_2 x+c1+c2,那么总传递量是
∑ i = 1 k ∣ x + ∑ j = 1 i − 1 c j ∣ \sum_{i=1}^{k}\left|x+\sum_{j=1}^{i-1}c_j\right| i=1kx+j=1i1cj
也就是 x x x 与每一个 c c c 的前缀和相加,求绝对值后相加。放在数轴上很容易发现,需要取 − x -x x 为中位数。

如果环是一个奇环呢?那么有一条边就仍然保持着 “同色才可操作” 的性质。不妨设白点比坑数量更多,则这一条边需要用来将白色删去。

注意到 白点的移动是没有顺序要求的,所以 “同时存在白球于顶点处,将其删去”,等同于 “分别存在过若干白球,将其删去”,也就是端点处额外需要若干个白球。将其直接加进 c c c 中,则问题变成了序列上移动,显然序列也是树的一种。

这样一来,我们对三种情况进行了讨论,并得到了一个 O ( n ) \mathcal O(n) O(n) 的做法。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+int(c^48);
	return a*int(f);
}
inline void writeint(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(char((x-x/10*10)^48));
}
inline int ABS(const int &x){
	return x < 0 ? -x : x;
}

const int MaxN = 100005;
struct Edge{
	int to, nxt;
	Edge(int T=0,int N=0){
		to = T, nxt = N;
	}
};
Edge e[MaxN<<1];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
	e[cntEdge] = Edge(b,head[a]);
	head[a] = cntEdge ++;
}

int deg[MaxN];
void topo(int x){
	deg[x] = 0;
	for(int i=head[x]; ~i; i=e[i].nxt){
		-- deg[e[i].to];
		if(deg[e[i].to] == 1)
			topo(e[i].to);
	}
}

int cnt[MaxN], sgn[MaxN];
int_ ans;
void scanTree(int x,int pre){
	cnt[x] = sgn[x];
	for(int i=head[x]; ~i; i=e[i].nxt){
		if(e[i].to == pre) continue;
		if(deg[e[i].to] > 0) continue;
		sgn[e[i].to] = -sgn[x];
		scanTree(e[i].to,x);
		ans += ABS(cnt[e[i].to]);
		cnt[x] += cnt[e[i].to];
	}
}

int sta[MaxN], top;
void traceCycle(int x){
	const int from = x;
	sta[top = 1] = from;
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(deg[e[i].to] > 0){
			x = e[i].to; break;
		}
	int pre = from; // not go back
	while(x != from){
		sta[++ top] = x;
		for(int i=head[x]; ~i; i=e[i].nxt)
			if(e[i].to != pre && deg[e[i].to] > 0){
				pre = x; x = e[i].to; break;
			}
	}
}

int sum[MaxN]; // prefix sum of cnt
int main(){
	int n = readint(), m = readint();
	memset(head+1,-1,n<<2);
	for(int i=1,a,b; i<=m; ++i){
		a = readint(), b = readint();
		addEdge(a,b), addEdge(b,a);
		++ deg[a], ++ deg[b];
	}
	if(m == n-1){ // Tree
		memset(deg+1,0,n<<2);
		sgn[1] = 1; scanTree(1,0);
		if(cnt[1]) puts("-1");
		else printf("%lld\n",ans);
		return 0; // done
	}
	rep(i,1,n) if(deg[i] == 1) topo(i);
	rep(i,1,n) if(deg[i] > 0){
		traceCycle(i); break;
	}
	sgn[sta[0] = 0] = -1;
	int all = 0; // all black-white
	for(int i=1; i<=top; ++i){
		sgn[sta[i]] = -sgn[sta[i-1]];
		scanTree(sta[i],0);
		all += cnt[sta[i]];
	}
	if((all&1) || (all && !(top&1)))
		return puts("-1")*0;
	cnt[sta[1]] -= (all>>1);
	cnt[sta[top]] -= (all>>1);
	ans += ABS(all>>1); // delete or make
	rep(i,1,top){
		if(top&1) ans += ABS(sum[i-1]);
		sum[i] = sum[i-1]+cnt[sta[i]];
	}
	if(!(top&1)){
		nth_element(sum+1,sum+(top+1)/2,sum+top+1);
		int mid = sum[(top+1)>>1]; // median
		rep(i,1,top) ans += ABS(mid-sum[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

后记

当我打完树的部分,测样例,过了。

我又去打环的部分,结果忘记让树的处理将 d e g deg deg 数组清空,却 一直没有回头重新将所有样例测一遍

最后只得了环的分。另外在这里留个坑:本场考试的次日,我又犯了同样的错误

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值