CSP-S 2023 T1密码锁 T2消消乐

T1 密码锁

对于一种状态,其可能产生的其他状态共有两种情况,一种情况是只转一个拨圈,这样产生的密码共有5∗9=455*9=4559=45种,另一种情况是转相邻的两个拨圈,这样产生的密码共有4∗9=364*9=3649=36种,所以一种状态能产生的密码共有45+36=8145+36=8145+36=81种。现在题目中给出了n(n≤8)n(n\leq 8)n(n8)个状态,问能满足所有这些状态的密码有多少种。
由于密码只有555位,所以总共的情况也只有10510^5105种,对于每种密码,我们只需要判断它是不是能到达给出的nnn个状态。判断时,我们遍历nnn个状态,对每个状态分别进行转一个拨圈的变换和转两个拨圈的变换,看是否能够得到该密码,若nnn个状态都符合条件,则答案加一。复杂度是完全可以接受的。

再讲一种复杂度较小的做法。对于每种状态(n≤8)(n\leq 8)(n8),枚举这个状态所能到达的所有密码(818181种),记录每种密码被枚举到的次数。枚举完nnn个状态能到达的密码后,遍历所有密码,若这个密码被枚举到的次数为nnn,也就说明每个状态都能到达这个密码,则答案加一。

#include <bits/stdc++.h>

using namespace std;
int n, m[10][6], ans;
map<string, int> mp;

int main(int argc, char const *argv[]) {
	cin >> n;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= 5; j++)
			cin >> m[i][j];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= 5; j++) {
			for (int k = 0; k < 10; k++) {
				string s = "";
				for (int l = 1; l < j; l++) s += char(m[i][l] + '0');
				if (m[i][j] == k) continue;
				s += char(k + '0');
				for (int l = j + 1; l <= 5; l++) s += char(m[i][l] + '0');
				mp[s]++;
			}
			if (j == 5) continue;
			for (int k = 1; k < 10; k++) {
				string s = "";
				for (int l = 1; l < j; l++) s += char(m[i][l] + '0');
				s += char((m[i][j] + k + 10) % 10 + '0');
				s += char((m[i][j + 1] + k + 10) % 10 + '0');
				for (int l = j + 2; l <= 5; l++) s += char(m[i][l] + '0');
				mp[s]++;
			}
		}
	for (auto i : mp)
		if (i.second == n)
			ans++;
	cout << ans << endl;
}

T2 消消乐

这个题的重点在于如何判断一个串是否可以被消除:用一个栈依次读串里的字符,若该字符与栈顶的元素相同,则弹出栈顶,否则把该元素放入栈,最后看栈是否为空即可,这样就得到了n3n^3n3的做法,有353535分。

// 35pts
#include <bits/stdc++.h>
#define A 100010

using namespace std;
int n, top, ans; string s;
char sta[A];

int main(int argc, char const *argv[]) {
	cin >> n >> s;
	for (int l = 0; l < n; l++)
		for (int r = l + 1; r < n; r++) {
			top = 0;
			for (int i = l; i <= r; i++) {
				if (sta[top] == s[i]) top--;
				else sta[++top] = s[i];
			}
			if (top == 0) ans++;
		}
	cout << ans << endl;
}

在上面的做法中,对于相同的左端点,我们枚举了所有的右端点,重复判断了许多次,其实只要从左端点开始走一遍到末尾就可以,每走一步都判断一下栈是否为空。复杂度降为n2n^2n2,有505050分。

// 50pts
#include <bits/stdc++.h>
#define A 200010

using namespace std;
int n, top, ans; string s;
char sta[A];

int main(int argc, char const *argv[]) {
	cin >> n >> s;
	for (int l = 0; l < n; l++) {
		top = 0;
		for (int r = l; r < n; r++) {
			if (sta[top] == s[r]) top--;
			else sta[++top] = s[r];
			if (top == 0) ans++;
		}
	}
	cout << ans << endl;
}

再看下一个部分分,“字符串中的每个字符独立等概率地从字符集中选择”,也就是说我们确定了左端点之后,在右端点不断增大的过程中,这个串会越来越长,能产生消除串的概率会越来越低,所以给右端点定一个界限,我们认为在这个界限之后就不可能产生消除串。这个界限根据左端点的个数来定,该部分分的数据范围下n≤2∗105n\leq2*10^5n2105,那就卡着能跑过的数据范围,把右端点的界限设置到1000100010002∗1082∗105\frac{2*10^8}{2*10^5}21052108=1000),这样能拿到606060分。

// 60pts
#include <bits/stdc++.h>
#define A 200010

using namespace std;
int n, top, ans; string s;
char sta[A];

int main(int argc, char const *argv[]) {
	cin >> n >> s;
	for (int l = 0; l < n; l++) {
		top = 0;
		int cnt = 1000;
		for (int r = l; r < n; r++) {
			if (sta[top] == s[r]) top--;
			else sta[++top] = s[r];
			if (top == 0) ans++;
			if (--cnt < 0 and n > 8000) break;
		}
	}
	cout << ans << endl;
}

其实能产生一个子串的条件是,这两个栈的状态是相同的。例如样例“accabccb”,初始时为空栈,代表的位置为000;“acca”为空栈,代表的位置为333;“accabccb”为空栈,代表的位置为777;这三个位置中每两个位置的组合都可以产生一个串,也就是C32C^2_3C32,共可以产生333个串。所以我们统计每种栈的状态出现的次数nnn,答案累计上Cn2=n(n−1)2C^2_n=\frac{n(n-1)}{2}Cn2=2n(n1)即可。这样可以拿到909090分,会有两个点MLEMLEMLE,就是刚才50−6050-605060分的两个点,原因是栈的状态太多,可能会产生大约nnn个字符串,串的长度从111递增到nnnmapmapmap占的空间太大。

// 90pts
#include <bits/stdc++.h>
#define A 2000010

using namespace std;
typedef long long ll;
int n, top;
string s, now;
char sta[A];
ll ans;
map<string, int> mp;

int main(int argc, char const *argv[]) {
	cin >> n >> s;
	mp[""]++;
	for (int i = 0; i < n; i++) {
		if (sta[top] == s[i]) top--, now.pop_back();
		else sta[++top] = s[i], now += s[i];
		mp[now]++;
	}
	for (auto i : mp)
		ans += 1ll * (i.second * 1ll * (i.second - 1ll) / 2);
	cout << ans << endl;
}

到这里可以想到把上面两个代码合一下就可以满分,所以现在问题来到了如何特判11、1211、121112这两个点。这两个点的条件上面也分析过了,“字符串中的每个字符独立等概率地从字符集中选择”,a−za-zaz262626个字母出现的概率是相同的,那我们就在输入字符串之后统计一下每个字母出现的次数,如果该字母出现的次数与期望次数的差值过大,那么我们认为这个点不是第111111个点或第121212个点。

// 100pts
#include <bits/stdc++.h>
#define A 2000010

using namespace std;
typedef long long ll;
int n, top, t[27];
string s, now;
char sta[A];
ll ans;
bool flag;
map<string, int> mp;

int main(int argc, char const *argv[]) {
	cin >> n >> s;
	for (int i = 0; i < n; i++) t[s[i] - 'a']++;
	for (int i = 0; i < 26; i++) // 判断测试点
		if (abs(t[i] - n / 26) > 10000) {
			flag = 1;
			break;
		}
	if (flag) {
		mp[""]++;
		for (int i = 0; i < n; i++) {
			if (sta[top] == s[i]) top--, now.pop_back();
			else sta[++top] = s[i], now += s[i];
			mp[now]++;
		}
		for (auto i : mp)
			ans += 1ll * (i.second * 1ll * (i.second - 1ll) / 2);
		cout << ans << endl;
	}
	else {
		for (int l = 0; l < n; l++) {
			top = 0;
			int cnt = 1000;
			for (int r = l; r < n; r++) {
				if (sta[top] == s[r]) top--;
				else sta[++top] = s[r];
				if (top == 0) ans++;
				if (--cnt < 0 and n > 8000) break;
			}
		}
		cout << ans << endl;
	}
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良月澪二

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值