AT2046「AGC004F」Namori

Address

AT2046

Algorithm 1

針對 M=N1M = N - 1 的數據,原圖是一棵樹。

因爲樹是二分圖,考慮對原圖進行二分圖染色,每次操作一定是選擇二分圖兩側的點。

考慮把問題進行轉化,初始時令二分圖某一側的點上有棋子,每次可以移動任意一個棋子到相鄰的沒有棋子的點上,詢問將所有棋子移到二分圖另一側的點上的最少操作次數。

顯然有解的必要條件是二分圖兩側的點數相同,我們需要證明其充分性。

考慮怎樣構造操作次數最少的方案,令二分圖某一側的點的權值爲 1-1,另一側的點的權值爲 11。任選一個點爲樹根,記 sumxsum_x 表示點 xx 所在子樹所有結點的權值和,不難發現點 xx 連向它父結點的那條邊的操作次數有一個下界 sumx|sum_x|

嘗試證明這個下界一定能夠達到,那麼對於根結點 rootroot,只要 sumroot=0sum_{root} = 0 就一定能構造出方案,充分性得證。

對於葉子結點,該結論顯然成立。考慮歸納,即已知所有子結點所在子樹的方案,我們將所有盈餘棋子和缺少棋子的子樹的方案相接,一定能夠兩兩抵消,最後盈餘或缺少的棋子數就爲 sumx|sum_x|,顯然一定要通過操作 xx 連向它父結點的那條邊來得到。

於是最後的答案就爲 i=1nsumi\sum \limits_{i = 1}^{n}|sum_i|,直接統計即可。

時間複雜度 O(n)\mathcal O(n)

Algorithm 2

針對 M=NM = N 的數據,原圖是一棵基環樹。

注意到環的長度的奇偶性會影響原圖是否是二分圖,因此對環的長度進行討論。

環的長度爲偶數

原圖仍然是一個二分圖,考慮環對操作次數的影響。

先求出環上每個點下掛着的子樹的權值和,記環長爲 mm,環上第 ii 個點所在子樹的權值和爲 AiA_itot=i=1mAitot = \sum \limits_{i = 1}^{m}A_i

同樣地,若 tot0tot \neq 0 無解。

類似樹中下界的證明,環上某一條邊棋子的移動方向在一種方案中是固定的。

設環上第 ii 條邊的經過的棋子量爲 xix_i(其絕對值表示經過的棋子的數目,正負號表示棋子移動的方向)。

可以得到如下的方程組:
{x1x2=A1x2x3=A2x3x4=A3...xm1xm=Am1xmx1=Am \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}
嘗試把所有 xix_i 都用 x1x_1 的表達式寫出來,我們需要最小化 i=1mxi\sum \limits_{i = 1}^{m}|x_i|
{x1=x10x2=x1A1x3=x1(A1+A2)...xm1=x1i=1m2Aixm=x1i=1m1Ai \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}
考慮把 yi=j=1i1Ai(1im)y_i = \sum \limits_{j = 1}^{i - 1}A_i(1 \le i \le m) 看做數軸上的點,我們需要最小化的即爲:
i=1mx1yi \begin{aligned}\sum \limits_{i = 1}^{m} |x_1 - y_i|\end{aligned}
這是一個經典問題,x1x_1yiy_i 的中位數即可。

環的長度爲奇數

原圖不再是一個二分圖。

考慮先把環上的某一條邊刪掉,同樣任選一個點爲樹根進行二分圖染色。

那麼多出來的這一條邊在轉化後的模型中就相當於在邊的兩個端點同時增加或減少一個棋子。

因此若 sumrootsum_{root} 爲奇數無解,且這條邊的操作次數固定,爲 sumroot2\dfrac{|sum_{root}|}{2}

因此我們在邊的兩個端點加上或減去對應數量的棋子後按照樹的方式處理即可。

總的時間複雜度 O(n)\mathcal 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章