The 2024 CCPC Online Contest



B - 军训 II#

题意#

n个人,第i个人身高为ai,定义不整齐度为所有区间的身高极差之和。求不整齐度的最小值以及现在的排列方案数。
不整齐度:l=0nr=lnmax(apl+apl+1,···,+apr)min(apl+apl+1,···,+apr)

思路#

按身高排序,此时不整齐度最小。身高相同的人可以换位置,k个身高相同的人可以有Akk种排列,将所有身高的排列数相乘后,再判断是否所有人身高都相同,否则可以有升序降序两种排序,额外乘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的字符串,其每个字符是从az的字母等概率随机选择的。将这些字符串插入字典树,求最多节点数以及期望节点数,对结果取模998244353,且期望节点数需要输出其分数形式的逆元。

思路#

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

代码#

点击查看代码
#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的序列ab,可以交换两序列中对应位置的数,并且可以操作任意次。输出无趣度最小时,无趣度大的那个序列,即输出最小的max{f(a),f(b)}
无趣度:f(A)=A1A2···An

思路#

可以观察到,如果当前两个数组的异或和分别是AB,则交换aibi的操作等价于AaibiBaibi,所以用aibi构造线性基。求出两序列的异或和AB,从高位往低位找,对于第i位:若都是1,且线性基这一位base[i]有值,则AB同时异或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 即可。先取完的获胜。两人都绝对聪明。问谁赢。

思路#

首先如果kn,先手可以直接拿走所有沙子,先手必胜。那么我们要找的是对于给定n,先手必胜所需k的最小值,从最小的1开始考虑,局面为1的奇数倍时必胜,必败时k增加到最小的必败情况,即1的最小偶数倍(2倍),即k=2,此时2的奇数倍必胜,否则k=2,第n轮由于2n的因数都是已经考虑过的情况,两人都不会选,所以每次循环两人都只会取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 @   _SeiI  阅读(253)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示