Loading

The 2024 CCPC Online Contest



B - 军训 II

题意

n个人,第i个人身高为\(a_i\),定义不整齐度为所有区间的身高极差之和。求不整齐度的最小值以及现在的排列方案数。
不整齐度:\(\sum_{l=0}^n\sum_{r=l}^n max(a_{pl} + a_{pl + 1},···,+a_{pr}) - min(a_{pl} + a_{pl + 1},···,+a_{pr})\)

思路

按身高排序,此时不整齐度最小。身高相同的人可以换位置,k个身高相同的人可以有\(A_k^k\)种排列,将所有身高的排列数相乘后,再判断是否所有人身高都相同,否则可以有升序降序两种排序,额外乘2。(注意\(n=1\)的情况,在这里WA了一发😭)

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
#define int long long

const int mod = 998244353;

void solve()
{
	int n;
	scanf("%lld", &n);
	vector<int> v(n);
	for (int i = 0; i < n; i++)
	{
		scanf("%lld", &v[i]);
	}
	if (n == 1) // 特判
	{
		printf("0 1");
		return;
	}
	sort(v.begin(), v.end());
	vector<vector<int>> maxn(n, vector<int>(n, 0)), minn(n, vector<int>(n, 1e6 + 1)); // minn[i][j]表示区间[i, j]的最小值
	int sum = 0; // 最小不整齐度
	for (int l = 0; l < n; l++)
	{
		maxn[l][l] = minn[l][l] = v[l];
		for (int r = l + 1; r < n; r++)
		{
			maxn[l][r] = max(maxn[l][r - 1], v[r]);
			minn[l][r] = min(minn[l][r - 1], v[r]);
			sum += maxn[l][r] - minn[l][r];
		}
	}
	// 全排列预处理
	vector<int> A(n + 1);
	A[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		A[i] = (A[i - 1] * i) % mod;
	}
	int ans = 1, k = 1; // 排列数(暂时认为升序降序两种),相邻重复数(至少有自己一个)
	bool f = false;
	for (int i = 1; i < n; i++)
	{
		if (v[i] == v[i - 1])
		{
			k++;
			if (k == n) // 全相同则升序降序排列相同
			{
				f = true;
			}
		}
		else
		{
			ans = (ans * A[k]) % mod;
			k = 1; // 重置
		}
	}
	if (k > 1) // 处理最后相同的部分
	{
		ans = (ans * A[k]) % mod;
	}
	if (!f)
	{
		ans = (ans * 2) % mod;
	}
	printf("%lld %lld", sum, ans);
}

signed main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}

	return 0;
}


D - 编码器-解码器

题意

思路

区间dp,贴个fzb的码

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int mxn = 1e2 + 5;
const int mod = 998244353;

int dp[mxn][mxn][mxn];

void solve()
{
	string s, t;
	cin >> s >> t;
	int n = s.size();
	int m = t.size();
	s = " " + s;
	t = " " + t;
	for (int i = 1; i <= m; i++)
	{
		dp[1][i][i] = (s[1] == t[i]);
	}
	for (int i = 2; i <= n; i++)
	{
		for (int len = 1; len <= m; len++)
		{
			if (len == 1)
			{
				for (int j = 1; j <= m; j++)
				{
					dp[i][j][j] = (2 * dp[i - 1][j][j] % mod + (s[i] == t[j])) % mod;
				}
				continue;
			}
			for (int l = 1; l + len - 1 <= m; ++l)
			{
				int r = l + len - 1;
				dp[i][l][r] = 2 * dp[i - 1][l][r] % mod;
				dp[i][l][r] %= mod;
				for (int k = l; k < r; ++k)
				{
					dp[i][l][r] += dp[i - 1][l][k] * dp[i - 1][k + 1][r] % mod;
					dp[i][l][r] %= mod;
				}
				for (int k = l; k <= r; ++k)
				{
					if (t[k] == s[i])
					{
						if (k == l)
						{
							dp[i][l][r] += dp[i - 1][k + 1][r];
						}
						else if (k == r)
						{
							dp[i][l][r] += dp[i - 1][l][k - 1];
						}
						else
						{
							dp[i][l][r] += dp[i - 1][l][k - 1] * dp[i - 1][k + 1][r] % mod;
						}
						dp[i][l][r] %= mod;
					}
				}
			}
		}
	}
	cout << dp[n][1][m] << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int T = 1;
	//cin >> T;
	while (T--)
	{
		solve();
	}

	return 0;
}

E - 随机过程

题意

\(n\)个长度为\(m\)的字符串,其每个字符是从\(a\)\(z\)的字母等概率随机选择的。将这些字符串插入字典树,求最多节点数以及期望节点数,对结果取模\(998244353\),且期望节点数需要输出其分数形式的逆元。

思路

对于最大节点数,考虑贪心。考虑深度为\(i\)的节点最多能有多少个。那么如果\(26^i ≤ n\),贡献为\(26^i\);否则贡献为\(\sum_{i=0}^m min(n, 26^i)\)
对于期望节点数,显然每一层中所有节点出现在 Trie 中的期望是一样的。考虑计算第\(i\)层某个特定点出现的概率,然后最后乘上总个数并求和即可。对于第\(i\)层某个节点,其不出现的概率为\((1 - 26^i)^n\)。因此答案为:\(\sum_{i=0}^m [\ 1 - (1 - \frac 1 {26^i})^n\ ]26^i\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long

const int mod = 998244353;

int qpow(int base, int x)
{
	int res = 1;
	while (x)
	{
		if (x & 1)
		{
			res *= base;
			res %= mod;
		}
		x >>= 1;
		base *= base;
		base %= mod;
	}
	return res;
}

void solve()
{
	int n, m;
	cin >> n >> m;
	int maxn = 0, ans = 0;
	for (int i = 0; i <= m; i++)
	{
		maxn += (i <= 3 && qpow(26, i) <= n ? qpow(26, i) : n);
		maxn %= mod;
	}
	for (int i = 0; i <= m; i++)
	{
		ans += (1 - (qpow(1 - qpow(qpow(26, i), mod - 2), n))) % mod * qpow(26, i) % mod;
		ans = (ans + mod) % mod; // 调为正数
	}
	cout << maxn << " " << ans << endl;
}

signed main() 
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int T = 1;
	//cin >> T;
	while (T--)
	{
		solve();
	}
	
	return 0;
}


J - 找最小

题意

给两个长为\(n\)的序列\(a\)\(b\),可以交换两序列中对应位置的数,并且可以操作任意次。输出无趣度最小时,无趣度大的那个序列,即输出最小的\(max\{ f(a), f(b) \}\)
无趣度:\(f(A) = A_1 \bigoplus A_2 \bigoplus ··· \bigoplus A_n\)

思路

可以观察到,如果当前两个数组的异或和分别是\(A\)\(B\),则交换\(a_i\)\(b_i\)的操作等价于\(A \bigoplus a_i \bigoplus b_i\)\(B \bigoplus a_i \bigoplus b_i\),所以用\(a_i \bigoplus b_i\)构造线性基。求出两序列的异或和\(A\)\(B\),从高位往低位找,对于第\(i\)位:若都是\(1\),且线性基这一位\(base[i]\)有值,则\(A\)\(B\)同时异或\(base[i]\),把这位变成\(0\)(尽可能化小);两个\(0\)就不处理下一位;剩下的情况只需要将较大的一方的这位化为\(0\)即可。

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

int base[32];

void insert(int x)
{
	for (int i = 30; i >= 0; i--)
	{
		if (x & (1LL << i))
		{
			if (!base[i])
			{
				base[i] = x;
				return;
			}
			x ^= base[i];
		}
	}
}

void solve()
{
	int n;
	cin >> n;
	fill(base, base + 32, 0);
	int A = 0, B = 0;
	vector<int> a(n), b(n);
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
		A ^= a[i];
	}
	for (int i = 0; i < n; i++)
	{
		cin >> b[i];
		B ^= b[i];
		insert(a[i] ^ b[i]);
	}
	int ans = max(A, B);
	for (int i = 30; i >= 0; i--)
	{
		if (!(A >> i & 1) && !(B >> i & 1)) // 两个0
		{
			continue;
		}
		if (A < B) 
		{
			swap(A, B);
		}
		if (A >> i & 1)
		{
			A ^= base[i];
			B ^= base[i];
		}
	}
	ans = min(ans, max(A, B));
	cout << ans << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int T = 1;
	//cin >> T;
	while (T--)
	{
		solve();
	}

	return 0;
}

K - 取沙子游戏

题意

n粒沙子,Alice与Bob轮流取沙子(Alice先手),要求要求取的数量不能超过 k,且必须为前面所有取的沙子数量的公因数(包括对手的)。Alice第一次取沙子时,沙子个数不受第二条限制,即不超过 k 即可。先取完的获胜。两人都绝对聪明。问谁赢。

思路

首先如果k\(\ge\)n,先手可以直接拿走所有沙子,先手必胜。那么我们要找的是对于给定n,先手必胜所需k的最小值,从最小的1开始考虑,局面为1的奇数倍时必胜,必败时k增加到最小的必败情况,即1的最小偶数倍(2倍),即k=2,此时2的奇数倍必胜,否则k=2,第n轮由于\(2^n\)的因数都是已经考虑过的情况,两人都不会选,所以每次循环两人都只会取k个沙子,判断过程和第一轮是一样的,所以可以每轮n/=2转化为第一轮的情况,t=2记录k的最小值。

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;

void solve()
{
	int n, k;
	scanf("%d%d", &n, &k);
	if (k >= n)
	{
		printf("Alice\n");
		return;
	}
	long long t = 1;
	while (true)
	{
		if (n & 1)
		{
			printf("Alice\n");
			return;
		}
		n /= 2;
		t *= 2;
		if (t > k)
		{
			printf("Bob\n");
			return;
		}
	}
}

signed main()
{
	int T = 1;
	scanf("%d", &T);
	while (T--)
	{
		solve();
	}

	return 0;
}


L - 网络预选赛

题意

给定(n * m)的矩阵,求其(2 * 2)子矩阵左到右,上到下是"ccpc"的数量。

思路

遍历每个元素作为子矩阵的左上角,再一次检查其他角即可。

代码

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;

char mp[505][505];

void solve()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; i++)
	{
		scanf("%s", &mp[i]);
	}
	int ans = 0;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			if (mp[i][j] == 'c' && mp[i][j + 1] == 'c' && mp[i + 1][j] == 'p' && mp[i + 1][j + 1] == 'c')
			{
				ans++;
			}
		}
	}
	printf("%d", ans);
}

signed main()
{
	int T = 1;
	//scanf("%d", &T);
	while (T--)
	{
		solve();
	}

	return 0;
}



比赛链接 https://mirror.codeforces.com/gym/105336

posted @ 2024-09-09 21:03  _SeiI  阅读(300)  评论(0)    收藏  举报