【CodeForces】Codeforces Round 623

比赛链接

点击打开链接

Problem A. Recommendations

考虑从最小的出现冲突的 a i a_i 开始,进行如下贪心:
保留 t i t_i 最大的 a i a_i ,将其余 a i a_i 增加 1 1

不难证明这个贪心的正确性。

因此,将所有元素按照 a i a_i 排序,用大根堆模拟这个过程即可。

时间复杂度 O ( N L o g N ) 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 = 0 k=0 的情况,此时答案显然为 0 0

我们希望关键的队伍存活尽可能长的时间,因此决赛中一定出现了关键队伍。
可以发现,除去第一轮比赛和决赛,剩余的比赛构成了两个相同的树形结构,其中胜者组的树形结构中,一个点代表一场比赛,败者组的树形结构中,一个点代表两场比赛。

在败者组的树形结构中,我们希望关键的队伍始终赢下比赛,因此若一场比赛中不存在关键队伍,其子树内也没有存在关键队伍的比赛。

考虑一支在第一轮中获胜的队伍,此时这支队伍将第一次进入败者组,若其对阵的是一支关键队伍,则它不会对答案产生贡献,否则,则说明这场比赛的子树内没有存在关键队伍的比赛,让这支队伍在第一轮输掉比赛是对答案贡献更大的选择。

因此,我们可以认为,第一场比赛获胜的队伍不会对败者组的比赛产生贡献。

由此,我们便可以设计动态规划解决问题了。

我们按照 DFS 序进行决策,在状态中记录当前考虑到的叶子节点,以及两棵树中上一个考虑到的点与当前点 LCA 的深度,预处理相邻叶子的 LCA 深度 n x t i nxt_i ,可以做到 O ( 1 ) O(1) 转移。

时间复杂度 O ( 2 N × N 2 ) 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 ( N 2 ) O(N^2) 种,可以借助后缀数组对所有子串进行排序,然后二分答案。

二分答案 S S 后,我们需要计算在分出的每一段字典序都不小于 S S 的划分方案数。
注意到在一个字符串后方加字符只会使字典序增大,从一个位置出发划分出一个合法的串的右端点是一个后缀。预处理这个后缀的位置,用前缀和优化 DP 即可。

时间复杂度 O ( N × M + N L o g N ) 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

不存在奇环说明图是二分图。

考虑如下算法:
令每个点以 1 2 \frac{1}{2} 的概率为二分图左侧的点, 1 2 \frac{1}{2} 的概率为二分图右侧的点。
在这张二分图上 DP ,找到最小的边数为 K K 的环。

其正确的概率应为 1 2 K 1 \frac{1}{2^{K-1}}
将其迭代多次,例如 1 0 4 10^4 次,即可保证其出错的概率在 1 0 9 10^{-9} 以内。

时间复杂度 O ( T × N 2 K ) O(T\times N^2K) ,其中 T T 为迭代次数。

#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

首先考虑如何将最终数组展开成为最短的原始数组。
则应当将最终数组倒序排列,生成 i i a i a_i 作为展开一层的结果。

由此:
K = 1 K=1 时,倒序的最终数组合法,当且仅当 a i N \sum a_i\leq N
K = 2 K=2 时,倒序的最终数组合法,当且仅当 i × a i N \sum i\times a_i\leq N

事实上,当 K 3 , N 2020 K\geq 3,N\leq 2020 时,合法的情况是很少的,可以直接用搜索解决,只需要优化一下 K = 3 K=3 时的展开方式,例如,可以展开一层,用以上判断标准来判断。

对于 K 2 K\leq 2 的情况,令
c n = a n , c n 1 = a n 1 a n , , c 1 = a 1 a 2 c_n=a_n,c_{n-1}=a_{n-1}-a_n,\dots,c_1=a_1-a_{2}

K = 1 K=1 时, c i c_i 对求和符号的贡献为 i × c i i\times c_i
K = 2 K=2 时, c i c_i 对求和符号的贡献为 i ( i + 1 ) 2 × c i \frac{i(i+1)}{2}\times c_i

由此,进行简单 DP 即可对 c i c_i 计数。

时间复杂度 O ( N 2 L o g N ) 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.

发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/104481370