【CodeForces】Codeforces Round 623

比賽鏈接

點擊打開鏈接

Problem A. Recommendations

考慮從最小的出現衝突的 aia_i 開始,進行如下貪心:
保留 tit_i 最大的 aia_i ,將其餘 aia_i 增加 11

不難證明這個貪心的正確性。

因此,將所有元素按照 aia_i 排序,用大根堆模擬這個過程即可。

時間複雜度 O(NLogN)O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n; ll ans, sum;
pair <int, int> a[MAXN];
priority_queue <int> Heap;
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i].first);
	for (int i = 1; i <= n; i++)
		read(a[i].second);
	sort(a + 1, a + n + 1);
	int cur = 0;
	for (int i = 1; i <= n; i++) {
		while (cur < a[i].first && Heap.size()) {
			ans += sum - Heap.top();
			sum -= Heap.top();
			Heap.pop();
			cur++;
		}
		cur = a[i].first;
		sum += a[i].second;
		Heap.push(a[i].second);
	}
	while (Heap.size()) {
		ans += sum - Heap.top();
		sum -= Heap.top();
		Heap.pop();
	}
	cout << ans << endl;
	return 0;
}

Problem B. Double Elimination

首先特判掉 k=0k=0 的情況,此時答案顯然爲 00

我們希望關鍵的隊伍存活儘可能長的時間,因此決賽中一定出現了關鍵隊伍。
可以發現,除去第一輪比賽和決賽,剩餘的比賽構成了兩個相同的樹形結構,其中勝者組的樹形結構中,一個點代表一場比賽,敗者組的樹形結構中,一個點代表兩場比賽。

在敗者組的樹形結構中,我們希望關鍵的隊伍始終贏下比賽,因此若一場比賽中不存在關鍵隊伍,其子樹內也沒有存在關鍵隊伍的比賽。

考慮一支在第一輪中獲勝的隊伍,此時這支隊伍將第一次進入敗者組,若其對陣的是一支關鍵隊伍,則它不會對答案產生貢獻,否則,則說明這場比賽的子樹內沒有存在關鍵隊伍的比賽,讓這支隊伍在第一輪輸掉比賽是對答案貢獻更大的選擇。

因此,我們可以認爲,第一場比賽獲勝的隊伍不會對敗者組的比賽產生貢獻。

由此,我們便可以設計動態規劃解決問題了。

我們按照 DFS 序進行決策,在狀態中記錄當前考慮到的葉子節點,以及兩棵樹中上一個考慮到的點與當前點 LCA 的深度,預處理相鄰葉子的 LCA 深度 nxtinxt_i ,可以做到 O(1)O(1) 轉移。

時間複雜度 O(2N×N2)O(2^N\times N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 18;
const int MAXS = (1 << 17) + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, nxt[MAXS];
bool f[MAXS];
void getnxt(int l, int r, int depth) {
	int mid = (l + r) / 2;
	nxt[mid] = depth;
	if (l != r) {
		getnxt(l, mid - 1, depth + 1);
		getnxt(mid + 1, r, depth + 1);
	}
}
int main() {
	read(n), read(m);
	if (m == 0) {
		puts("0");
		return 0;
	}
	int goal = 1 << n;
	for (int i = 1; i <= m; i++) {
		int x; read(x);
		f[x] = true;
	}
	if (n >= 3) getnxt(1, (1 << (n - 2)) - 1, 1);
	int ans = 1;
	for (int i = 1; i <= goal; i += 2)
		ans += f[i] || f[i + 1];
	static int dp[MAXN][MAXN], tmp[MAXN][MAXN];
	memset(dp, 0, sizeof(dp));
	for (int p = 1, q = 1; p <= goal; p += 4, q++) {
		memset(tmp, 0, sizeof(tmp));
		int cnt = f[p] + f[p + 1] + f[p + 2] + f[p + 3];
		for (int i = 0; i <= n - 1; i++)
		for (int j = 0; j <= n - 1; j++) {
			if (cnt >= 2) chkmax(tmp[nxt[q]][nxt[q]], dp[i][j] + (n - 1 - i) + 2 * (n - 1 - j));
			else if (cnt == 1) {
				chkmax(tmp[nxt[q]][min(j, nxt[q])], dp[i][j] + (n - 1 - i));
				chkmax(tmp[min(i, nxt[q])][nxt[q]], dp[i][j] + 2 * (n - 1 - j));
			} else chkmax(tmp[min(i, nxt[q])][min(j, nxt[q])], dp[i][j]);
		}
		memcpy(dp, tmp, sizeof(tmp));
	}
	cout << ans + dp[0][0] << endl;
	return 0;
}

Problem C. Au Pont Rouge

顯然,答案只有 O(N2)O(N^2) 種,可以藉助後綴數組對所有子串進行排序,然後二分答案。

二分答案 SS 後,我們需要計算在分出的每一段字典序都不小於 SS 的劃分方案數。
注意到在一個字符串後方加字符只會使字典序增大,從一個位置出發劃分出一個合法的串的右端點是一個後綴。預處理這個後綴的位置,用前綴和優化 DP 即可。

時間複雜度 O(N×M+NLogN)O(N\times M+NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int MAXM = 1e6 + 5;
const long long INF = 2e18;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
namespace SuffixArray {
	const int MAXN = 100005;
	const int MAXLOG = 20;
	const int MAXC = 256; 
	int sa[MAXN], rnk[MAXN], height[MAXN];
	int Min[MAXN][MAXLOG], bit[MAXN], N;
	void init(char *a, int n) {
		N = n, a[n + 1] = 0;
		for (int i = 0; i <= n + 1; i++)
			sa[i] = rnk[i] = 0;
		static int x[MAXN], y[MAXN], cnt[MAXN], rk[MAXN];
		memset(cnt, 0, sizeof(cnt));
		for (int i = 1; i <= n; i++)
			cnt[(int) a[i]]++;
		for (int i = 1; i <= MAXC; i++)
			cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
			sa[cnt[(int) a[i]]--] = i;
		rnk[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			rnk[sa[i]] = rnk[sa[i - 1]] + (a[sa[i]] != a[sa[i - 1]]);
		for (int k = 1; rnk[sa[n]] != n; k <<= 1) {
			for (int i = 1; i <= n; i++) {
				x[i] = rnk[i];
				y[i] = (i + k <= n) ? rnk[i + k] : 0;
			}
			memset(cnt, 0, sizeof(cnt));
			for (int i = 1; i <= n; i++)
				cnt[y[i]]++;
			for (int i = 1; i <= n; i++)
				cnt[i] += cnt[i - 1];
			for (int i = n; i >= 1; i--)
				rk[cnt[y[i]]--] = i;
			memset(cnt, 0, sizeof(cnt));
			for (int i = 1; i <= n; i++)
				cnt[x[i]]++;
			for (int i = 1; i <= n; i++)
				cnt[i] += cnt[i - 1];
			for (int i = n; i >= 1; i--)
				sa[cnt[x[rk[i]]]--] = rk[i];
			rnk[sa[1]] = 1;
			for (int i = 2; i <= n; i++)
				rnk[sa[i]] = rnk[sa[i - 1]] + (x[sa[i]] != x[sa[i - 1]] || y[sa[i]] != y[sa[i - 1]]);		
		}
		int now = 0;
		for (int i = 1; i <= n; i++) {
			if (now) now--;
			while (a[i + now] == a[sa[rnk[i] + 1] + now]) now++;
			height[rnk[i]] = now;
		}
		for (int i = 1; i <= n; i++)
			Min[i][0] = height[i];
		for (int p = 1; p < MAXLOG; p++) {
			int tmp = 1 << (p - 1);
			for (int i = 1, j = tmp + 1; j <= n; i++, j++)
				Min[i][p] = min(Min[i][p - 1], Min[i + tmp][p - 1]);
		}
		for (int i = 1; i <= n; i++) {
			bit[i] = bit[i - 1];
			if (i >= 1 << (bit[i] + 1)) bit[i]++;
		}
	}
	int lcp(int x, int y) {
		if (x == y) return N - x + 1;
		x = rnk[x], y = rnk[y];
		if (x > y) swap(x, y);
		int tmp = bit[y - x];
		return min(Min[x][tmp], Min[y - (1 << tmp)][tmp]);
	}
}
int n, m, tot; ll k;
pair <int, int> a[MAXM];
char s[MAXN];
bool cmp(pair <int, int> a, pair <int, int> b) {
	int tmp = SuffixArray :: lcp(a.first, b.first);
	chkmin(tmp, a.second - a.first + 1);
	chkmin(tmp, b.second - b.first + 1);
	if (tmp == a.second - a.first + 1 && tmp == b.second - b.first + 1) return false;
	if (tmp == a.second - a.first + 1) return false;
	if (tmp == b.second - b.first + 1) return true;
	return s[a.first + tmp] > s[b.first + tmp];
}
int nxt[MAXN]; ll dp[MAXN][MAXN];
ll calc(pair <int, int> a) {
	for (int i = 1; i <= n; i++) {
		nxt[i] = i + 1;
		while (nxt[i] <= n + 1 && cmp(a, make_pair(i, nxt[i] - 1))) nxt[i]++;
	}
	memset(dp, 0, sizeof(dp));
	dp[0][1] = 1, dp[0][2] = -1;
	for (int i = 0; i <= m - 1; i++) {
		for (int j = 1; j <= n + 1; j++) {
			dp[i][j] += dp[i][j - 1];
			chkmin(dp[i][j], INF);
		}
		for (int j = 1; j <= n; j++) {
			dp[i + 1][nxt[j]] += dp[i][j];
			chkmin(dp[i + 1][nxt[j]], INF);
		}
	}
	for (int i = 1; i <= n + 1; i++) {
		dp[m][i] += dp[m][i - 1];
		chkmin(dp[m][i], INF);
	}
	return dp[m][n + 1];
}
int main() {
	read(n), read(m), read(k);
	scanf("%s", s + 1);
	SuffixArray :: init(s, n);
	for (int i = 1; i <= n; i++)
	for (int j = i; j <= n; j++)
		a[++tot] = make_pair(i, j);
	sort(a + 1, a + tot + 1, cmp);
	int l = 1, r = tot;
	while (l < r) {
		int mid = (l + r) / 2;
		if (calc(a[mid]) >= k) r = mid;
		else l = mid + 1;
	}
	for (int i = a[l].first; i <= a[l].second; i++)
		putchar(s[i]);
	puts("");
	return 0;
}

Problem D. Tourism

不存在奇環說明圖是二分圖。

考慮如下算法:
令每個點以 12\frac{1}{2} 的概率爲二分圖左側的點, 12\frac{1}{2} 的概率爲二分圖右側的點。
在這張二分圖上 DP ,找到最小的邊數爲 KK 的環。

其正確的概率應爲 12K1\frac{1}{2^{K-1}}
將其迭代多次,例如 10410^4 次,即可保證其出錯的概率在 10910^{-9} 以內。

時間複雜度 O(T×N2K)O(T\times N^2K) ,其中 TT 爲迭代次數。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 85;
const int MAXK = 12;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, k, a[MAXN][MAXN];
bool side[MAXN]; int dp[MAXK][MAXN];
int main() {
	srand('X' + 'Y' + 'X');
	read(n), read(k);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		read(a[i][j]);
	int ans = INT_MAX;
	while (clock() <= 2.0 * CLOCKS_PER_SEC) {
		for (int i = 0; i <= k; i++)
		for (int j = 1; j <= n; j++)
			dp[i][j] = INT_MAX;
		dp[0][1] = 0;
		for (int i = 1; i <= n; i++)
			side[i] = rand() % 2 == 0;
		for (int i = 1; i <= k; i++)
		for (int j = 1; j <= n; j++) {
			int tmp = dp[i - 1][j];
			if (tmp < INT_MAX) {
				for (int l = 1; l <= n; l++)
					if (side[l] ^ side[j]) chkmin(dp[i][l], dp[i - 1][j] + a[j][l]);
			}
		}
		chkmin(ans, dp[k][1]);
	}
	cout << ans << endl;
	return 0;
}

Problem E. Strange Function

首先考慮如何將最終數組展開成爲最短的原始數組。
則應當將最終數組倒序排列,生成 iiaia_i 作爲展開一層的結果。

由此:
K=1K=1 時,倒序的最終數組合法,當且僅當 aiN\sum a_i\leq N
K=2K=2 時,倒序的最終數組合法,當且僅當 i×aiN\sum i\times a_i\leq N

事實上,當 K3,N2020K\geq 3,N\leq 2020 時,合法的情況是很少的,可以直接用搜索解決,只需要優化一下 K=3K=3 時的展開方式,例如,可以展開一層,用以上判斷標準來判斷。

對於 K2K\leq 2 的情況,令
cn=an,cn1=an1an,,c1=a1a2c_n=a_n,c_{n-1}=a_{n-1}-a_n,\dots,c_1=a_1-a_{2}

K=1K=1 時, cic_i 對求和符號的貢獻爲 i×cii\times c_i
K=2K=2 時, cic_i 對求和符號的貢獻爲 i(i+1)2×ci\frac{i(i+1)}{2}\times c_i

由此,進行簡單 DP 即可對 cic_i 計數。

時間複雜度 O(N2LogN)O(N^2LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2025;
const int P = 998244353;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int ans, n, k;
vector <int> a;
bool ok(vector <int> a) {
	sort(a.begin(), a.end());
	if (k == 3) {
		int sum = 0;
		for (auto x : a) sum += x;
		if (sum > n) return false;
		vector <int> b; int tmp = 0;
		reverse(a.begin(), a.end());
		for (auto x : a) {
			tmp++;
			while (x--) b.push_back(tmp);
		}
		a = b; tmp = sum = 0;
		reverse(a.begin(), a.end());
		for (auto x : a) {
			tmp++;
			sum += tmp * x;
		}
		return sum <= n;
	}
	for (int i = 1; i <= k; i++) {
		int sum = 0;
		for (auto x : a) sum += x;
		if (sum > n) return false;
		vector <int> b; int tmp = 0;
		reverse(a.begin(), a.end());
		for (auto x : a) {
			tmp++;
			while (x--) b.push_back(tmp);
		}
		a = b;
	}
	return true;
}
void work(int pos, int now) {
	a.push_back(now);
	if (ok(a)) {
		ans++;
		work(pos + 1, now);
	}
	a.pop_back();
	if (now != 1) work(pos, now - 1);
}
int dp[MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(k);
	if (k == 1) {
		for (int i = 1; i <= n; i++)
			dp[i][n - i] = 1;
		for (int i = n; i >= 1; i--)
		for (int j = 0; j <= n; j++) {
			int tmp = dp[i][j];
			if (tmp != 0) {
				for (int k = 0; i * k <= j; k++)
					update(dp[i - 1][j - i * k], tmp);
			}
		}
		int ans = 0;
		for (int i = 0; i <= n; i++)
			update(ans, dp[0][i]);
		cout << ans << endl;
	} else if (k == 2) {
		for (int i = 1; i * (i + 1) / 2 <= n; i++)
			dp[i][n - i * (i + 1) / 2] = 1;
		for (int i = n; i >= 1; i--)
		for (int j = 0; j <= n; j++) {
			int tmp = dp[i][j];
			if (tmp != 0) {
				for (int k = 0; i * (i + 1) / 2 * k <= j; k++)
					update(dp[i - 1][j - i * (i + 1) / 2 * k], tmp);
			}
		}
		int ans = 0;
		for (int i = 0; i <= n; i++)
			update(ans, dp[0][i]);
		cout << ans << endl;
	} else {
		work(1, n);
		cout << ans << endl;
	}
	return 0;
}

Problem F. Bad Cryptography

Maybe it's better to put the problem like "You are given a field, implement Pohlig-hellman algorithm on it" to an educational round.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章