AT2046「AGC004F」Namori

Address

AT2046

Algorithm 1

针对 M = N − 1 M = N - 1 M=N1 的数据,原图是一棵树。

因为树是二分图,考虑对原图进行二分图染色,每次操作一定是选择二分图两侧的点。

考虑把问题进行转化,初始时令二分图某一侧的点上有棋子,每次可以移动任意一个棋子到相邻的没有棋子的点上,询问将所有棋子移到二分图另一侧的点上的最少操作次数。

显然有解的必要条件是二分图两侧的点数相同,我们需要证明其充分性。

考虑怎样构造操作次数最少的方案,令二分图某一侧的点的权值为 − 1 -1 1,另一侧的点的权值为 1 1 1。任选一个点为树根,记 s u m x sum_x sumx 表示点 x x x 所在子树所有结点的权值和,不难发现点 x x x 连向它父结点的那条边的操作次数有一个下界 ∣ s u m x ∣ |sum_x| sumx

尝试证明这个下界一定能够达到,那么对于根结点 r o o t root root,只要 s u m r o o t = 0 sum_{root} = 0 sumroot=0 就一定能构造出方案,充分性得证。

对于叶子结点,该结论显然成立。考虑归纳,即已知所有子结点所在子树的方案,我们将所有盈余棋子和缺少棋子的子树的方案相接,一定能够两两抵消,最后盈余或缺少的棋子数就为 ∣ s u m x ∣ |sum_x| sumx,显然一定要通过操作 x x x 连向它父结点的那条边来得到。

于是最后的答案就为 ∑ i = 1 n ∣ s u m i ∣ \sum \limits_{i = 1}^{n}|sum_i| i=1nsumi,直接统计即可。

时间复杂度 O ( n ) \mathcal O(n) O(n)

Algorithm 2

针对 M = N M = N M=N 的数据,原图是一棵基环树。

注意到环的长度的奇偶性会影响原图是否是二分图,因此对环的长度进行讨论。

环的长度为偶数

原图仍然是一个二分图,考虑环对操作次数的影响。

先求出环上每个点下挂着的子树的权值和,记环长为 m m m,环上第 i i i 个点所在子树的权值和为 A i A_i Ai t o t = ∑ i = 1 m A i tot = \sum \limits_{i = 1}^{m}A_i tot=i=1mAi

同样地,若 t o t ≠ 0 tot \neq 0 tot=0 无解。

类似树中下界的证明,环上某一条边棋子的移动方向在一种方案中是固定的。

设环上第 i i i 条边的经过的棋子量为 x i x_i xi(其绝对值表示经过的棋子的数目,正负号表示棋子移动的方向)。

可以得到如下的方程组:
{ x 1 − x 2 = A 1 x 2 − x 3 = A 2 x 3 − x 4 = A 3 . . . x m − 1 − x m = A m − 1 x m − x 1 = A m \begin{cases}x_1 - x_2 &= A_1 \\x_2 - x_3 &= A_2 \\x_3 - x_4 &= A_3 \\&... \\x_{m - 1} - x_{m} &= A_{m - 1} \\x_m - x_1 &= A_m \\ \end{cases} x1x2x2x3x3x4xm1xmxmx1=A1=A2=A3...=Am1=Am
尝试把所有 x i x_i xi 都用 x 1 x_1 x1 的表达式写出来,我们需要最小化 ∑ i = 1 m ∣ x i ∣ \sum \limits_{i = 1}^{m}|x_i| i=1mxi
{ x 1 = x 1 − 0 x 2 = x 1 − A 1 x 3 = x 1 − ( A 1 + A 2 ) . . . x m − 1 = x 1 − ∑ i = 1 m − 2 A i x m = x 1 − ∑ i = 1 m − 1 A i \begin{cases}x_1 &= x_1 - 0 \\x_2 &= x_1 - A_1 \\x_3 &= x_1 - (A_1 + A_2) \\&...\\x_{m - 1} &= x_1 - \sum \limits_{i = 1}^{m - 2}A_i\\x_m &= x_1 - \sum \limits_{i = 1}^{m - 1}A_i\end{cases} x1x2x3xm1xm=x10=x1A1=x1(A1+A2)...=x1i=1m2Ai=x1i=1m1Ai
考虑把 y i = ∑ j = 1 i − 1 A i ( 1 ≤ i ≤ m ) y_i = \sum \limits_{j = 1}^{i - 1}A_i(1 \le i \le m) yi=j=1i1Ai(1im) 看做数轴上的点,我们需要最小化的即为:
∑ i = 1 m ∣ x 1 − y i ∣ \begin{aligned}\sum \limits_{i = 1}^{m} |x_1 - y_i|\end{aligned} i=1mx1yi
这是一个经典问题, x 1 x_1 x1 y i y_i yi 的中位数即可。

环的长度为奇数

原图不再是一个二分图。

考虑先把环上的某一条边删掉,同样任选一个点为树根进行二分图染色。

那么多出来的这一条边在转化后的模型中就相当于在边的两个端点同时增加或减少一个棋子。

因此若 s u m r o o t sum_{root} sumroot 为奇数无解,且这条边的操作次数固定,为 ∣ s u m r o o t ∣ 2 \dfrac{|sum_{root}|}{2} 2sumroot

因此我们在边的两个端点加上或减去对应数量的棋子后按照树的方式处理即可。

总的时间复杂度 O ( n ) \mathcal O(n) O(n)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
}

using std::vector;
typedef long long ll;
const int N = 1e5 + 5;

vector<int> e[N];
bool in_cir[N], vis[N], flag;
int sum[N], b[N];
int col[N], fa[N], sze[N], cir[N];
int n, m, rt, cm, bm;

template <class T>
inline T Abs(T x) {return x < 0 ? -x : x;}

inline void dfs(int x, int Fa)
{
	sum[x] = col[x] == 0 ? 1 : -1;
	for (int i = 0, im = e[x].size(); i < im; ++i)
	{
		int y = e[x][i];
		if (y == Fa)
			continue ;
		col[y] = col[x] ^ 1;
		dfs(y, x);
		sum[x] += sum[y];
	}
}

inline void work1()
{
	for (int i = 1, u, v; i < n; ++i)
	{
		read(u); read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	col[1] = 1;
	dfs(1, 0);
	if (sum[1] != 0)
		puts("-1");
	else
	{
		ll ans = 0;
		for (int i = 1; i <= n; ++i)
			ans += Abs(sum[i]);
		std::cout << ans << std::endl;
	}
}

inline int ufs_find(int x)
{
	if (fa[x] != x)
		return fa[x] = ufs_find(fa[x]);
	return x;
}

inline void ufs_merge(int x, int y)
{
	int tx = ufs_find(x),
		ty = ufs_find(y);
	if (tx != ty)
	{
		if (sze[tx] > sze[ty])
			std::swap(tx, ty);
		fa[tx] = ty;
		sze[ty] += sze[tx];
	}
}

inline void find_circle(int x, int Fa, int des)
{
	cir[++cm] = x;
	if (x == des)
	{
		flag = true; 
		return ;
	}
	for (int i = 0, im = e[x].size(); i < im; ++i)
	{
		int y = e[x][i];
		if (y == Fa)
			continue ;
		find_circle(y, x, des);
		if (flag)
			return ;
	}
	--cm;
} 

inline void work2()
{
	for (int i = 1; i <= n; ++i)
		fa[i] = i, sze[i] = 1;
	for (int i = 1, u, v; i <= n; ++i)
	{
		read(u); read(v);
		if (ufs_find(u) == ufs_find(v))
		{
			rt = u;
			find_circle(u, 0, v);
		}
		else
		{
			ufs_merge(u, v);
			e[u].push_back(v);
			e[v].push_back(u);
		}
	}
	dfs(rt, 0);
	if (cm & 1)
	{
		if (Abs(sum[rt]) & 1)
			puts("-1");
		else
		{
			ll tmp = sum[rt] / 2, ans = 0;
			ans += Abs(tmp);
			for (int i = 1; i <= cm; ++i)
				sum[cir[i]] -= tmp;
			sum[rt] -= tmp;
			for (int i = 1; i <= n; ++i)
				ans += Abs(sum[i]);
			std::cout << ans << std::endl;
		}
	}
	else
	{
		if (sum[rt] != 0)
			puts("-1");
		else
		{
			ll ans = 0;
			for (int i = 1; i <= cm; ++i)
				b[++bm] = sum[cir[i]];
			std::sort(b + 1, b + bm);
			for (int i = 1; i <= cm; ++i)
				sum[cir[i]] = b[bm >> 1] - sum[cir[i]];
			for (int i = 1; i <= n; ++i)
				ans += Abs(sum[i]);
			std::cout << ans << std::endl;
		}
	}
}

int main()
{
	read(n); read(m);
	if (m == n - 1)
		work1();
	else
		work2();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值