【CodeForces】CodeForces Round #402 (Div. 1) 题解

本文详细解析了CodeForces Round #402 (Div. 1) 比赛中的五道题目,包括思路要点和代码实现。题目涉及字符串操作、位运算、图论、几何和动态规划等算法问题,通过二分查找、枚举和深度优先搜索等方法进行求解,复杂度分析涵盖O(NLogN)到O(N^5)不等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【比赛链接】

【题解链接】

**【A】**String Game

【思路要点】

  • 显然可以二分答案。
  • 然后判定 p p p是否为删减后的 t t t的子序列即可。
  • 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, a[MAXN];
char s[MAXN], t[MAXN];
bool flg[MAXN];
bool check(int mid) {
	memset(flg, 0, sizeof(flg));
	for (int i = 1; i <= mid; i++)
		flg[a[i]] = true;
	int pos = 1;
	for (int i = 1; i <= m; i++) {
		while (pos <= n && !(!flg[pos] && s[pos] == t[i])) pos++;
		if (pos > n) return false; pos++;
	}
	return true;
}
int main() {
	scanf("\n%s\n%s", s + 1, t + 1);
	n = strlen(s + 1);
	m = strlen(t + 1);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	int l = 0, r = n - m;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	writeln(l);
	return 0;
}

**【B】**Bitwise Formula

【思路要点】

  • 容易发现问题对于每一位是独立的。
  • 对于每一位,枚举其取值 ( 0 / 1 ) (0/1) (0/1),模拟一遍题目中的过程,取较最优者作为答案。
  • 时间复杂度 O ( N ∗ M ) O(N*M) O(NM)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, now[MAXN];
int x[MAXN], y[MAXN];
string name, rubbish, Min, Max;
string opt[MAXN], val[MAXN];
map <string, int> num;
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++) {
		cin >> name;
		num[name] = i;
		cin >> rubbish;
		cin >> val[i];
		if (val[i][0] == '0' || val[i][0] == '1') continue;
		else {
			x[i] = num[val[i]];
			cin >> opt[i];
			cin >> rubbish;
			y[i] = num[rubbish];
		}
	}
	for (int j = 0; j < m; j++) {
		int sumz = 0, sumo = 0;
		now[0] = 0;
		for (int i = 1; i <= n; i++) {
			if (opt[i] == "OR") now[i] = now[x[i]] | now[y[i]];
			else if (opt[i] == "AND") now[i] = now[x[i]] & now[y[i]];
			else if (opt[i] == "XOR") now[i] = now[x[i]] ^ now[y[i]];
			else now[i] = val[i][j] - '0';
			sumz += now[i];
		}
		now[0] = 1;
		for (int i = 1; i <= n; i++) {
			if (opt[i] == "OR") now[i] = now[x[i]] | now[y[i]];
			else if (opt[i] == "AND") now[i] = now[x[i]] & now[y[i]];
			else if (opt[i] == "XOR") now[i] = now[x[i]] ^ now[y[i]];
			else now[i] = val[i][j] - '0';
			sumo += now[i];
		}
		if (sumz <= sumo) Min += '0';
		else Min += '1';
		if (sumz >= sumo) Max += '0';
		else Max += '1';
	}
	cout << Min << endl;
	cout << Max << endl;
	return 0;
}

**【C】**Peterson Polyglot

【思路要点】

  • 我们本质上希望求出的是对于每一个点 i i i,若将其所有儿子全部删除,将其孙子连接至其下并整理(合并相同的字符)后会消失的节点数 b e n i f i t i benifit_i benifiti,如果得到了 b e n i f i t i benifit_i benifiti,那么我们只需要找到 b e n i f i t i benifit_i benifiti的总和最大的一层节点,将它们的儿子删去即可。

  • 考虑一个直观的做法:

    对于每个点 i i i,首先令 b e n i f i t i benifit_i benifiti为其儿子数。

    考虑整理字典树的过程,将其儿子中所有带有出边 c h ( c h = a , b , c , . . . , z ) ch(ch=a,b,c,...,z) ch(ch=a,b,c,...,z)的点找到,令这样的点数为 c n t cnt cnt,若 c n t ≥ 2 cnt≥2 cnt2,将 b e n i f i t i benifit_i benifiti增加 c n t − 1 cnt-1 cnt1,并递归整理这些点的 c h ch ch出边指向的节点。

  • 考虑其复杂度,可以发现,该算法的整理部分涉及到的总点数为 O ( ∑ b e n i f i t i ) O(\sum benifit_i) O(benifiti)

    并且在点 i i i处被整理的点数不会超过以点 i i i为根的子树大小减去点 i i i最大的子树的大小。

    也就是说,一个点一旦在 ∑ b e n i f i t i \sum benifit_i benifiti中被计算一次,计算它的子树大小就会至少翻倍。

    因此,一个点在 ∑ b e n i f i t i \sum benifit_i benifiti中被计算的次数为 O ( L o g N ) O(LogN) O(LogN)次,该算法的整理部分涉及到的总点数为 O ( ∑ b e n i f i t i ) = O ( N L o g N ) O(\sum benifit_i)=O(NLogN) O(benifiti)=O(NLogN)

  • 时间复杂度 O ( 26 ∗ N L o g N ) O(26*NLogN) O(26NLogN)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 300005;
const int MAXC = 26;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct edge {int dest; char val; };
vector <edge> a[MAXN];
vector <int> p[MAXN];
int n, benifit[MAXN];
int child[MAXN][MAXC], depth[MAXN];
int calc(int depth) {
	if (p[depth].size() <= 1) return 0;
	int ans = 0;
	for (int j = 0; j < 26; j++) {
		p[depth + 1].clear();
		int cnt = 0;
		for (unsigned i = 0; i < p[depth].size(); i++)
			if (child[p[depth][i]][j]) {
				cnt++;
				p[depth + 1].push_back(child[p[depth][i]][j]);
			}
		ans += max(cnt - 1, 0) + calc(depth + 1);
	}
	return ans;
}
void dfs(int pos, int fa) {
	depth[pos] = depth[fa] + 1;
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (a[pos][i].dest != fa) {
			child[pos][a[pos][i].val - 'a'] = a[pos][i].dest;
			dfs(a[pos][i].dest, pos);
		}
}
int main() {
	read(n);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; char z;
		scanf("%d%d %c", &x, &y, &z);
		a[x].push_back((edge) {y, z});
		a[y].push_back((edge) {x, z});
	}
	dfs(1, 0);
	for (int i = 1; i <= n; i++) {
		p[0].clear();
		for (int j = 0; j < MAXC; j++)
			if (child[i][j]) {
				benifit[depth[i]]++;
				p[0].push_back(child[i][j]);
			}
		benifit[depth[i]] += calc(0);
	}
	int ans = 1;
	for (int i = 1; i <= n; i++)
		if (benifit[i] > benifit[ans]) ans = i;
	writeln(n - benifit[ans]);
	writeln(ans);
	return 0;
}

**【D】**Parquet Re-laying

【思路要点】

  • 我们发现难以轻易地构造出一组无解的情况,因此我们猜想所有合法的状态都是能够相互到达的。

  • 由于操作可逆,我们可以试图将给出的两个状态分别归一与一个统一的中间状态。

  • N N N M M M必然至少有一个是偶数,不妨令 N N N为偶数( M M M为偶数的情况是对称的),我们希望将每个状态归一于所有地板竖直摆放的状态。

  • 考虑最左边的列,若所有地板已经竖直摆放,那么将这列删去(不予考虑)

    否则,最左边的列可能包含一些水平摆放的地板的左半段,并且由于 N N N是偶数,这样的左半段的个数也为偶数,它们两两之间由竖直摆放的地板连接,相隔的距离也为偶数。

    若一对竖直对齐的水平摆放的地板中间(两列)仅有竖直摆放的地板,那么显然我们可以将这些地板通过操作变为水平摆放的,再将这一段内的所有地板通过操作变为竖直摆放的,这样就消除了一对竖直对齐的水平摆放的地板,使用的操作次数是 O ( N ) O(N) O(N)的。

    可以证明若当前还存在水平摆放的地板,那么一定存在上述的一对竖直对齐的水平摆放的地板,使得它们中间仅有竖直摆放的地板。证明较为显然,因为一对竖直对齐的水平摆放的地板中间若存在水平摆放的地板,一定可以找到一对距离更近的竖直对齐的水平摆放的地板。

  • 那么算法就很明显了,暴力找到上述的一对竖直对齐的水平摆放的地板,花费 O ( N ) O(N) O(N)的操作次数消除它们,重复执行直到不存在水平摆放的地板。

  • 执行次数至多 O ( N 2 ) O(N^2) O(N2),单次花费时间至多 O ( N 3 ) O(N^3) O(N3)

  • 时间复杂度 O ( N 5 ) O(N^5) O(N5),使用操作次数上限 O ( N 3 ) O(N^3) O(N3)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m;
char s[MAXN][MAXN];
vector <int> ansx[2], ansy[2];
void getplan(vector <int> &ansx, vector <int> &ansy) {
	if (n % 2 == 0) {
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cnt += s[i][j] == 'L';
		while (cnt != 0) {
			int px = 0, py = 0, qx = 0, qy = 0;
			for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				if (s[i][j] == 'L') {
					bool flg = false;
					for (int k = i + 1; k <= n; k++)
						if (s[k][j] == 'L') {
							flg = true;
							qx = k, qy = j;
							break;
						} else if (s[k][j] == 'R' || s[k][j + 1] == 'L') break;
					if (flg) px = i, py = j;
				}
			if (px == 0) {
				printf("Error!\n");
				exit(0);
			}
			for (int i = px + 1; i <= qx - 1; i += 2) {
				ansx.push_back(i);
				ansy.push_back(py);
			}
			for (int i = px; i <= qx; i += 2) {
				ansx.push_back(i);
				ansy.push_back(py);
				s[i][py] = s[i][py + 1] = 'U';
				s[i + 1][py] = s[i + 1][py + 1] = 'D';
			}
			cnt -= 2;
		}
	} else {
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cnt += s[i][j] == 'U';
		while (cnt != 0) {
			int px = 0, py = 0, qx = 0, qy = 0;
			for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				if (s[i][j] == 'U') {
					bool flg = false;
					for (int k = j + 1; k <= m; k++)
						if (s[i][k] == 'U') {
							flg = true;
							qx = i, qy = k;
							break;
						} else if (s[i][k] == 'D' || s[i + 1][k] == 'U') break;
					if (flg) px = i, py = j;
				}
			if (px == 0) {
				printf("Error!\n");
				exit(0);
			}
			for (int i = py + 1; i <= qy - 1; i += 2) {
				ansx.push_back(px);
				ansy.push_back(i);
			}
			for (int i = py; i <= qy; i += 2) {
				ansx.push_back(px);
				ansy.push_back(i);
				s[px][i] = s[px + 1][i] = 'L';
				s[px][i + 1] = s[px + 1][i + 1] = 'R';
			}
			cnt -= 2;
		}
	}
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		scanf("\n%s", s[i] + 1);
	getplan(ansx[0], ansy[0]);
	for (int i = 1; i <= n; i++)
		scanf("\n%s", s[i] + 1);
	getplan(ansx[1], ansy[1]);
	writeln(ansx[0].size() + ansx[1].size());
	for (unsigned i = 0; i < ansx[0].size(); i++)
		printf("%d %d\n", ansx[0][i], ansy[0][i]);
	reverse(ansx[1].begin(), ansx[1].end());
	reverse(ansy[1].begin(), ansy[1].end());
	for (unsigned i = 0; i < ansx[1].size(); i++)
		printf("%d %d\n", ansx[1][i], ansy[1][i]);
	return 0;
}

**【E】**Selling Numbers

【思路要点】

  • 由于要考虑进位的问题,我们考虑从低位向高位DP。
  • 我们发现无论如何,我们加上的数是对于每个 B i B_i Bi相同的,因此 B i B_i Bi相对的大小关系不会产生变化。
  • 那么,无论我们加上什么数,从第 i i i位向第 i + 1 i+1 i+1位有进位的数一定是第 i i i位到第一位组成的数较大的一些数,因此,对于每一位,可能的产生进位的集合只有 O ( N ) O(N) O(N)个。
  • d p i , j dp_{i,j} dpi,j表示已经考虑了所有数较低的 i i i位,其中有 j j j个数产生了进位时,能够获得的最大价值。
  • d p i , ∗ dp_{i,*} dpi,为一层,从 d p i , ∗ dp_{i,*} dpi, d p i + 1 , ∗ dp_{i+1,*} dpi+1,之间的转移我们可以较为简单地做到 O ( 10 ∗ N ) O(10*N) O(10N)的时间复杂度,完成一层的转移后,对所有数第 i + 1 i+1 i+1位到第一位组成的数重新排序即可。
  • 时间复杂度 O ( N ∗ M a x { ∣ A ∣ , ∣ B i ∣ } ∗ ( L o g N + 10 ) ) O(N*Max\{|A|,|B_i|\}*(LogN+10)) O(NMax{A,Bi}(LogN+10))

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int INF = 1e9;
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;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, Max, type, val[MAXN], len[MAXN], home[MAXN];
int a[MAXN][MAXN], b[MAXN], dp[MAXN][MAXN];
char s[MAXN];
bool cmp(int x, int y) {
	if (a[x][type] == a[y][type]) return home[x] < home[y];
	else return a[x][type] > a[y][type];
}
int main() {
	scanf("%s", s + 1), read(n);
	m = strlen(s + 1);
	reverse(s + 1, s + m + 1);
	for (int i = 1; i <= m; i++)
		if (s[i] == '?') b[i] = -1;
		else b[i] = s[i] - '0';
	int Max = m;
	for (int i = 1; i <= n; i++) {
		scanf("\n%s", s + 1);
		len[i] = strlen(s + 1);
		reverse(s + 1, s + len[i] + 1);
		for (int j = 1; j <= len[i]; j++)
			a[i][j] = s[j] - '0';
		chkmax(len[i], m);
		chkmax(Max, len[i]);
	}
	Max++;
	for (int i = 0; i <= 9; i++)
		read(val[i]);
	for (int i = 0; i <= Max; i++)
	for (int j = 0; j <= n; j++)
		dp[i][j] = -INF;
	dp[0][0] = 0;
	static int now[MAXN];
	for (int i = 1; i <= n; i++)
		now[i] = i;
	for (int i = 1; i <= Max; i++) {
		int from, to;
		if (b[i] == -1) {
			if (i == m) from = 1, to = 9;
			else from = 0, to = 9;
		} else from = to = b[i];
		for (int ch = from; ch <= to; ch++) {
			int sum = 0, cnt = 0;
			for (int j = 1; j <= n; j++)
				if (i <= len[j]) {
					sum += val[(a[j][i] + ch) % 10];
					if (a[j][i] + ch >= 10) cnt++;
				}
			chkmax(dp[i][cnt], dp[i - 1][0] + sum);
			for (int j = 1; j <= n; j++) {
				if (i <= len[now[j]]) {
					sum -= val[(a[now[j]][i] + ch) % 10];
					if (a[now[j]][i] + ch >= 10) cnt--;
				}
				sum += val[(a[now[j]][i] + ch + 1) % 10];
				if (a[now[j]][i] + ch + 1 >= 10) cnt++;
				chkmax(dp[i][cnt], dp[i - 1][j] + sum);
			}
		}
		for (int j = 1; j <= n; j++)
			home[now[j]] = j;
		type = i;
		sort(now + 1, now + n + 1, cmp);
	}
	writeln(dp[Max][0]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值