AtCoder Regular Contest 125


題目傳送門:AtCoder Regular Contest 125

A - Dial Up

題意簡述

給定一個長度為 \(n\) 的 01 串 \(s\),和一個長度為 \(m\) 的 01 串 \(t\)

你有一個 01 串 \(b\),初始時為空串。你可以執行兩種操作:

  • 向左或者向右對 \(s\) 循環移位。
  • \(s\) 的首字符添加到 \(b\) 的末尾。

問是否可能能把 \(b\) 變成 \(t\),如果可行,求出最少的操作次數。

數據范圍:\(1 \le n, m \le 2 \times {10}^5\)

AC 代碼
#include <cstdio>
#include <algorithm>

const int MN = 200005;
const int MM = 200005;

int N, M;
int S[MN], T[MM];
int A, B;

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i)
		scanf("%d", &S[i]), A += S[i];
	for (int i = 1; i <= M; ++i)
		scanf("%d", &T[i]), B += T[i];
	if ((A == 0 && B == 0) || (A == N && B == M))
		return printf("%d\n", M), 0;
	if (A == 0 || A == N)
		return puts("-1"), 0;
	if ((S[1] == 0 && B == 0) || (S[1] == 1 && B == M))
		return printf("%d\n", M), 0;
	int Ans = N;
	for (int i = 2; i <= N; ++i)
		if (S[i] != S[1])
			Ans = std::min({Ans, i - 2, N - i});
	T[0] = S[1];
	for (int i = 1; i <= M; ++i)
		Ans += T[i] != T[i - 1];
	printf("%d\n", Ans + M);
	return 0;
}

無解是容易判斷的:如果 \(s\) 全為一種字符,而 \(t\) 中含有至少一個另一種字符則無解。

添加字符的操作需要恰好執行 \(m\) 次,輸出答案的時候加上就行了,那么就是要求出最少的循環移位次數

如果 \(t\) 全為一種字符,而且恰好就是初始時的 \(s\) 的首字符,那就不需要循環移位,直接輸出 \(m\)

否則是需要至少一次循環移位到另一種字符上的,這一定是選取向左或向右碰到的最近的一個不同的字符

在第一次需要循環移位的時候(也就是 \(t\) 中第一個不等於 \(s\) 的首字符的字符)移動到那里,然后接下來的每一次切換字符(即 \(t_i \ne t_{i - 1}\))只需要在那個位置周圍抖動一下即可。

據此容易寫出代碼。

時間復雜度為 \(\mathcal O (n + m)\)

B - Squares

題意簡述

給定正整數 \(n\),求滿足 \(1 \le x, y \le n\)\(x^2 - y\) 為完全平方數的數對 \((x, y)\) 的數量,對 \(998244353\) 取模。

數據范圍:\(1 \le n \le {10}^{12}\)

AC 代碼
#include <cstdio>
#include <cmath>

typedef long long LL;
const int Mod = 998244353;

LL N;

int main() {
	scanf("%lld", &N);
	LL S = 0, D = 0, Ans = 0;
	for (int i = 1; ; ++i) {
		S += 2 * i - 1;
		D += 2;
		if (S > N)
			break;
		Ans += (N - S) / D + 1;
	}
	printf("%lld\n", Ans % Mod);
	return 0;
}

要讓 \(x^2 - y\) 為完全平方數 \(z^2\)\(z \ge 0\)),且 \(1 \le x, y \le n\),那么一定有 \(0 \le z < x \le n\),且 \(x^2 - z^2 \le n\)

可以發現就是對差值 \(\le n\) 的完全平方數數對計數,對 \(x \le n\) 的限制是不必需的,因為如果存在 \(x^2 - z^2 \le n\)(滿足 \(0 \le z < x\))則必然有 \(x \le \frac{n + 1}{2}\),而在數據范圍下必然有 \(\frac{n + 1}{2} \le n\)

即求出此序列中有多少對不同的數的差 \(\le n\)\([0, 1, 4, 9, 16, 25, 36, 49, \ldots ]\)

我們考慮做此轉換:將序列差分得到 \([1, 3, 5, 7, 9, \ldots ]\),這是一個等差數列。

然后即是求這個新序列中有多少段非空區間的和 \(\le n\),然后對區間長度分開考慮:

  • 區間長度為 \(1\):即是求 \([1, 3, 5, 7, 9, \ldots ]\) 中有多少個數 \(\le n\)
  • 區間長度為 \(2\):即是求 \([4, 8, 12, 16, 20, \ldots ]\) 中有多少個數 \(\le n\)
  • 區間長度為 \(3\):即是求 \([9, 15, 21, 27, 33, \ldots ]\) 中有多少個數 \(\le n\)

可以發現每一個都是等差數列,所以每個的答案是很好通過公式計算的,總答案就是每個的答案加起來。

而可以發現每個等差數列的首項都是完全平方數,所以只要枚舉 \(\sqrt{n}\) 次這樣的等差數列即可。

時間復雜度為 \(\mathcal O \!\left( \sqrt{n} \right)\)

C - LIS to Original Sequence

題意簡述

給出一個長度為 \(k\) 的整數序列 \(a_1, a_2, \ldots , a_k\) 滿足此序列嚴格遞增且值域在 \([1, n]\) 內。

可以證明一定存在一個 \(1 \sim n\) 的排列 \(p_1, p_2, \ldots , p_n\),滿足序列 \(a\) 是它的最長上升子序列(LIS)之一

在所有滿足條件的序列 \(p\) 中,輸出字典序最小的那個。

數據范圍:\(1 \le k \le n \le 2 \times {10}^5\)

AC 代碼
#include <cstdio>

const int MN = 200005;

int N, K;
int B[MN];

int main() {
	scanf("%d%d", &N, &K);
	int j = 1;
	for (int i = 1; i < K; ++i) {
		int x;
		scanf("%d", &x);
		printf("%d ", x);
		B[x] = 1;
		while (B[j]) ++j;
		if (j < x) {
			printf("%d ", j);
			B[j] = 1;
			while (B[j]) ++j;
		}
	}
	for (int i = N; i >= j; i--)
		if (!B[i])
			printf("%d%c", i, " \n"[i == j]);
	return 0;
}

要讓字典序最小,我們肯定首先貪心地進行考慮。

顯然不能有 \(p_1 < a_1\),否則加入 \(p_1\) 的話就會增加 LIS 的長度。

\(k = 1\) 時顯然只能有 \(p = [n, n - 1, \ldots , 2, 1]\),否則 LIS 長度將至少為 \(2\)

但是我們可以證明當 \(k \ge 2\) 時,一定可以取 \(p_1 = a_1\) 而仍然存在滿足條件的 \(p\)。一個證明角度如下:

  • \(p\) 的第 \(1\) 段為 \(a_1, a_1 - 1, \ldots , 1\)
  • \(p\) 的第 \(2\) 段為 \(a_2, a_2 - 1, \ldots , a_1 + 1\)
  • ……
  • \(p\) 的第 \(k - 1\) 段為 \(a_{k - 1}, a_{k - 1} - 1, \ldots , a_{k - 2} + 1\)
  • \(p\) 的最后一段為 \(n, n - 1, \ldots , a_k, a_k - 1, \ldots , a_{k - 1} + 1\)

舉個例子,如果 \(a = [3, 5, 6, 8]\)\(n = 9\),令 \(p = [3, 2, 1, 5, 4, 6, 9, 8, 7]\)

按照上述分段方式,每一段內都是遞減的,所以 LIS 在每一段中最多取一個,而存在一種取法取得到序列 \(a\),所以滿足條件。

我們已經讓 \(p_1\) 滿足字典序盡量小了。但是我們肯定不滿足於當 \(a_1 \ge 2\) 時讓 \(p_2 = a_1 - 1\)。如果此時讓 \(p_2 = 1\) 會如何呢?

我們考慮上文的分段論證法,如果我們仍然可以讓整個序列 \(p\) 分成 \(k\) 段,每段內都遞減,而 \(a_1 \sim a_k\) 依次存在於每一段內,則此時的 \(p\) 一定是滿足條件的。

所以按照此原理,顯然當 \(a_1 \ge 2\) 時是可以讓 \(p_2 = 1\) 的。即有:

  • 如果 \(a_1 = 1\),則 \(p_1 = a_1 = 1\)
  • 如果 \(a_1 \ge 2\),則 \(p_1 = a_1\)\(p_2 = 1\)

但是對於再下一個元素(如果 \(a_1 = 1\) 那就是 \(p_2\) 否則就是 \(p_3\))呢?

它當然可以為 \(a_2\)(如果 \(k \ge 3\)),這是由上面的構造保證的,但是它又不能在 \([a_1 + 1, a_2 - 1]\) 中,否則會增加 LIS 的長度。

那么我們要減小字典序,就是說要讓它小於 \(a_1\),對於 \(a_1 = 1\) 來說這是不可能的,對於 \(a_1 \ge 2\) 來說這也是不合法的,否則 \([p_2 = 1 , p_3, a_2, a_3, \ldots , a_k]\) 就會成為更長的 LIS。

那么也就是說只能有下一個元素為 \(a_2\)(如果 \(k \ge 3\))。如果 \(k = 2\) 的話后面就只能從 \(n\) 一路遞減下來了。

將這樣的過程推廣到 \(a\) 后面的每一項(除了最后一項 \(a_k\)),就是:

  • \(p\) 的下一個元素為 \(a_i\)
  • 如果最小的可用元素小於 \(a_i\),則 \(p\) 的下一個元素為這個最小的可用元素。
  • \(i\) 自增 \(1\),直到 \(i = k\) 為止,此時 \(p\) 的尾部填充從 \(n\) 遞減的可用元素。

這樣的構造方法,由於上文的分段論證法,是合法的。而它的字典序最小性由每一步的貪心保證。

找最小可用元素維護一個指針即可。時間復雜度為 \(\mathcal O (n)\)

D - Unique Subsequence

題意簡述

給定一個長度為 \(n\) 的序列 \(a_1, a_2, \ldots , a_n\),且 \(1 \le a_i \le n\),但可以有重復元素。

求出在它之中作為子序列只出現過恰好一次的非空序列個數,對 \(998244353\) 取模。

注:只出現過恰好一次指的是僅存在唯一下標序列滿足 \(a\) 對應下標的子序列為此序列。

數據范圍:\(1 \le n \le 2 \times {10}^5\)

AC 代碼
#include <cstdio>

const int Mod = 998244353;
const int MN = 200005;

int N;
int l[MN], v[MN], b[MN];

inline void Add(int i, int x) {
	for (; i <= N; i += i & -i)
		b[i] = (b[i] + x) % Mod;
}
inline int Qur(int i) {
	int s = 0;
	for (; i; i -= i & -i)
		s = (s + b[i]) % Mod;
	return s;
}

int main() {
	scanf("%d", &N);
	int s = 0;
	for (int i = 1; i <= N; ++i) {
		int x;
		scanf("%d", &x);
		int f;
		if (l[x]) {
			f = (s - Qur(l[x] - 1) + Mod) % Mod;
			Add(l[x], Mod - v[x]);
			s = (s - v[x] + Mod) % Mod;
		} else
			f = s + 1;
		Add(i, f);
		s = (s + f) % Mod;
		l[x] = i;
		v[x] = f;
	}
	printf("%d\n", s);
	return 0;
}

我們肯定首先考慮子序列自動機的結構。

可以發現這基本上就是要求“正着走子序列自動機得到的下標字典序最小的子序列位置”,與“反着走得到的下標字典序最大的位置”是一樣的。

可以證明這個條件等價於:在子序列自動機上走每一步的時候,例如 \(p_i \to p_{i + 1}\),這之中必須滿足 \(a\) 在下標區間 \([p_i + 1, p_{i + 1} - 1]\) 中不存在另一個 \(a_{p_i}\)。否則 \(p_i\) 就可以移動到那個位置上而保證還是相同的子序列,但下標集合不唯一。同時還需要保證它在子序列自動機最終到達的位置 \(p_k\) 是這個數值 \(a_{p_k}\) 的最后一次出現。

轉換一下角度,這即是說 \(p_i\)\(p_{i + 1}\) 的上一次出現 \(a_{p_i}\) 的位置,這和子序列自動機的“\(p_{i + 1}\)\(p_i\) 的下一次出現 \(a_{p_{i + 1}}\) 的位置”是對稱的。

也就是說要做 DAG 轉移的話,假設當前位置是 \(\boldsymbol{i}\),上一次出現位置是 \(\boldsymbol{\mathit{last}}\),那么只接收來自 \(\boldsymbol{[\mathit{last}, i - 1]}\) 中的,是當前最后一次出現的數值的位置的轉移

我們發現這就是限定一段區間內的轉移求和,並且會實時更新轉移的數組(每計算一個新位置就要把上一次出現位置的轉移取消掉)。

這可以通過樹狀數組實現。時間復雜度為 \(\mathcal O (n \log n)\)

E - Snack

題意簡述

\(n\) 種小球,第 \(i\) 種小球的顏色為 \(i\) 並且總共有 \(a_i\) 個。

\(m\) 個盒子,第 \(i\) 個盒子中,每一種顏色的小球都不能超過 \(b_i\) 個,而總小球數不能超過 \(c_i\) 個。

求最多能在盒子里裝入多少小球。

數據范圍:\(1 \le n, m \le 2 \times {10}^5\)

AC 代碼
#include <cstdio>
#include <algorithm>
#include <functional>
#include <climits>

typedef long long LL;
const int MN = 200005;
const int MM = 200005;

int N, M;
LL A[MN], B[MM], C[MM];
int t[MM], per[MM];

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i) scanf("%lld", &A[i]);
	for (int i = 1; i <= M; ++i) scanf("%lld", &B[i]);
	for (int i = 1; i <= M; ++i) scanf("%lld", &C[i]);
	for (int i = 1; i <= M; ++i) {
		LL tmp = N - C[i] / B[i];
		if (tmp < 0) tmp = 0;
		t[i] = tmp;
		per[i] = i;
	}
	std::sort(per + 1, per + M + 1, [](int i, int j) {
		return t[i] < t[j];
	});
	std::sort(A + 1, A + N + 1);
	LL Ans = LLONG_MAX;
	LL SumC = 0, SumB = 0, SumA = 0;
	for (int i = 1; i <= M; ++i) SumC += C[i];
	int j = 1;
	for (int i = 0; i <= N; ++i) {
		while (j <= M && t[per[j]] == i) {
			SumC -= C[per[j]];
			SumB += B[per[j]];
			++j;
		}
		if (i != 0)
			SumA += A[i];
		Ans = std::min(Ans, SumA + SumC + SumB * (N - i));
	}
	printf("%lld\n", Ans);
	return 0;
}

顯然這是一個網絡最大流模型。如圖所示:

根據最大流最小割定理,我們轉成最小割考慮。

如果左邊割掉 \(x\) 條邊,右邊割掉 \(y\) 條邊,那么就是左邊沒割掉的每個點,和右邊沒割掉的每個點,中間的邊都要被割掉。

花費即是:

\[\begin{aligned} &= \sum_{i \in X} a_i + \sum_{i \in Y} c_i + \sum_{i \in (N \setminus X)} \sum_{j \in (M \setminus Y)} b_j \\ &= \sum_{i \in X} a_i + \sum_{i \in Y} c_i + \sum_{i \in (M \setminus Y)} b_i (n - x) \\ &= \sum_{i \in X} a_i + \sum_{i = 1}^{m} \begin{cases} c_i & \text{, if } (i, T) \text{ is cut} \\ b_i (n - x) & \text{, if } (i, T) \text{ is not cut} \end{cases} \end{aligned} \]

再注意到,左側的 \(a_i\),只要確定了 \(x\),如何選取是不影響右側的,由於是求最小割,所以一定是按 \(a_i\) 從小到大排序后取前綴。

再注意到,右側的每個點可以自由選擇\(T\) 之間的邊是否被割,也就是可以選取 \(\min(c_i, b_i (n - x))\)

對於一個固定的 \(i\),可以解得當 \(\displaystyle x \ge n - \frac{c_i}{b_i}\) 時才會有 \(b_i (n - x) \le c_i\),否則 \(c_i < b_i (n - x)\)

\(\displaystyle t_i = \max \!\left( \left\lceil n - \frac{c_i}{b_i} \right\rceil\! , 0 \right)\! = \max \!\left( n - \!\left\lfloor \frac{c_i}{b_i} \right\rfloor\! , 0 \right)\),即如果 \(0 \le x < t_i\) 則取 \(c_i\),否則取 \(b_i (n - x)\)

則按照 \(t_i\) 從小到大排序,從小到大枚舉 \(x\),可以雙指針統計答案。

時間復雜度為 \(\mathcal O (n \log n + m \log m)\)

F - Tree Degree Subset Sum

題意簡述

有一棵 \(n\) 個結點的無根樹,令第 \(i\) 個結點的度數為 \(d_i\)

求滿足條件的數對 \((x, y)\) 的數量:存在一個大小為 \(x\) 的點集使得其中結點的度數之和為 \(y\)

數據范圍:\(2 \le n \le 2 \times {10}^5\)

題意簡述
#include <cstdio>
#include <functional>

typedef long long LL;
const int Inf = 0x3f3f3f3f;
const int MN = 200005;

int N, deg[MN], buk[MN];

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= 2 * N - 2; ++i) {
		int x;
		scanf("%d", &x);
		++deg[x];
	}
	for (int i = 1; i <= N; ++i)
		++buk[deg[i] - 1];
	static int f[MN];
	for (int i = 1; i <= N - 2; ++i) f[i] = Inf;
	for (int i = 1; i <= N - 2; ++i) if (buk[i]) {
		int len = buk[i] + 1;
		static int g[MN];
		for (int j = 0; j < i; ++j) {
			static int que[MN];
			int head = 1, tail = 0;
			for (int k = j; k <= N - 2; k += i) {
				auto v = [&](int x){ return f[x] - x / i; };
				while (head <= tail && v(que[tail]) > v(k)) --tail;
				que[++tail] = k;
				while (que[head] / i <= k / i - len) ++head;
				g[k] = v(que[head]) + k / i;
			}
		}
		for (int k = 0; k <= N - 2; ++k) f[k] = g[k];
	}
	LL Ans = 0;
	for (int i = 0; i <= N - 2; ++i) Ans += std::max((N - f[N - 2 - i]) - f[i] + 1, 0);
	printf("%lld\n", Ans);
	return 0;
}

我們發現樹本身並沒有什么用,計算出來度數序列后就可以扔了。

這有點像是個背包問題,但是我們不可能存儲兩維的信息。

看起來 \(\displaystyle \sum_{i = 1}^{n} d_i = 2 n - 2\) 的性質應該是個突破口。

對於這類總和固定的正整數序列,我們知道其中不同的數的個數是 \(\mathcal O \!\left( \sqrt{n} \right)\) 的。但是對本題幫助不大。

我們知道一棵大小大於等於 \(2\) 的樹一定有葉子結點。

對應到序列中就是當長度為 \(n\) 的正整數序列的總和為 \(2 n - 2\) 時一定有某個 \(d_i = 1\)

如果從葉子結點的角度考慮,每加入一個葉子節點,可以讓 \(x, y\) 均增加 \(1\),反之均減少 \(1\)

也就是 \(y - x\) 的值是不變的,不過這樣比較麻煩,我們令所有 \(d_i\) 均減少 \(1\),這樣就變成非負整數序列的總和為 \(n - 2\)

這樣的話就是 \(y\) 不變,\(x\) 自由變化了。要求的東西的數量也不受影響。

關鍵點來了:我們猜想,對於一個固定的 \(y\) 值,對應的滿足條件的 \(x\) 值要么不存在,要么形成一段連續區間。證明:

  • 令這個 \(y\) 對應的最小 \(x\)\(m(y)\),而最大 \(x\)\(M(y)\)
  • 我們即是要證明 \(M(y) - m(y) \le 2 z\),其中 \(z\)\(d_i = 0\) 的數目。
  • 這是因為在 \(x\) 取值最小的方案中,權值為 \(0\) 的元素肯定一個都沒取;而在 \(x\) 取值最大的方案中,權值為 \(0\) 的元素肯定全部取到了。
  • 所以調整最小的方案可以取到 \([m(y), m(y) + z]\) 之間的所有 \(x\),而調整最大的方案可以取到 \([M(y) - z, M(y)]\) 之間的所有 \(x\)
  • 如果有 \(M(y) - m(y) \le 2 z\) 成立,這兩個區間的並即是 \([m(y), M(y)]\)。接下來證明此式確實成立:
  • 我們可以說明任意情況下都有 \(-z \le y - x \le z - 2\),於是當 \(y\) 固定時所有可能的 \(x\) 之間最多差 \(2 z - 2\)
  • 我們考慮 \(y - x\) 的意義就是每個元素的權值再減去 \(1\),即權值 \(\ge -1\) 且總和為 \(-2\) 的情況,而權值恰好為 \(-1\) 的元素有 \(z\) 個。
  • 顯然,權值非負的數的權值總和恰好為 \(z - 2\)(因為 \(-2 - z \cdot (-1) = z - 2\)),所以取得再多也不能超過 \(z - 2\)
  • 所以確實有 \(-z \le y - x \le z - 2\)。證畢。

那么我們只需要求出所有的 \(M(y)\)\(m(y)\) 即可。這就可以通過背包求了,但是朴素做復雜度是 \(\mathcal O (n^2)\) 的。

不過注意到不同的數的個數是 \(\mathcal O \!\left( \sqrt{n} \right)\) 的,所以相當於做多重背包,用單調隊列優化即可。

注意要判對於一個 \(y\) 區間為空的情況,即不存在合法的 \(x\)

一個常數優化手段是注意到如果區間不為空的話,\(M(y) = n - m(n - 2 - y)\),所以不用做兩次背包 DP。

這題中好像二進制分組優化多重背包比直接單調隊列跑得快,雖然理論上多一個 \(\log\),我對此並不太確定是什么原理。jiangly 指正這里不多 \(\log\),因為總和為 \(s\) 的正整數序列 \(a\)\(\sum \log c_k\)\(\mathcal O \!\left( \sqrt{n} \right)\),其中 \(c_k\) 表示 \(a_i = k\) 的個數。

時間復雜度:\(\mathcal O \! \left( n \sqrt{n} \right)\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM