对于一种状态,其可能产生的其他状态共有两种情况,一种情况是只转一个拨圈,这样产生的密码共有 5 ∗ 9 = 45 5*9=45 5∗9=45种,另一种情况是转相邻的两个拨圈,这样产生的密码共有 4 ∗ 9 = 36 4*9=36 4∗9=36种,所以一种状态能产生的密码共有 45 + 36 = 81 45+36=81 45+36=81种。现在题目中给出了 n ( n ≤ 8 ) n(n\leq 8) n(n≤8)个状态,问能满足所有这些状态的密码有多少种。
由于密码只有 5 5 5位,所以总共的情况也只有 1 0 5 10^5 105种,对于每种密码,我们只需要判断它是不是能到达给出的 n n n个状态。判断时,我们遍历 n n n个状态,对每个状态分别进行转一个拨圈的变换和转两个拨圈的变换,看是否能够得到该密码,若 n n n个状态都符合条件,则答案加一。复杂度是完全可以接受的。
再讲一种复杂度较小的做法。对于每种状态 ( n ≤ 8 ) (n\leq 8) (n≤8),枚举这个状态所能到达的所有密码( 81 81 81种),记录每种密码被枚举到的次数。枚举完 n n n个状态能到达的密码后,遍历所有密码,若这个密码被枚举到的次数为 n n n,也就说明每个状态都能到达这个密码,则答案加一。
#include
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;
}
这个题的重点在于如何判断一个串是否可以被消除:用一个栈依次读串里的字符,若该字符与栈顶的元素相同,则弹出栈顶,否则把该元素放入栈,最后看栈是否为空即可,这样就得到了 n 3 n^3 n3的做法,有 35 35 35分。
// 35pts
#include
#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;
}
在上面的做法中,对于相同的左端点,我们枚举了所有的右端点,重复判断了许多次,其实只要从左端点开始走一遍到末尾就可以,每走一步都判断一下栈是否为空。复杂度降为 n 2 n^2 n2,有 50 50 50分。
// 50pts
#include
#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 ∗ 1 0 5 n\leq2*10^5 n≤2∗105,那就卡着能跑过的数据范围,把右端点的界限设置到 1000 1000 1000( 2 ∗ 1 0 8 2 ∗ 1 0 5 \frac{2*10^8}{2*10^5} 2∗1052∗108=1000),这样能拿到 60 60 60分。
// 60pts
#include
#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”,初始时为空栈,代表的位置为 0 0 0;“acca”为空栈,代表的位置为 3 3 3;“accabccb”为空栈,代表的位置为 7 7 7;这三个位置中每两个位置的组合都可以产生一个串,也就是 C 3 2 C^2_3 C32,共可以产生 3 3 3个串。所以我们统计每种栈的状态出现的次数 n n n,答案累计上 C n 2 = n ( n − 1 ) 2 C^2_n=\frac{n(n-1)}{2} Cn2=2n(n−1)即可。这样可以拿到 90 90 90分,会有两个点 M L E MLE MLE,就是刚才 50 − 60 50-60 50−60分的两个点,原因是栈的状态太多,可能会产生大约 n n n个字符串,串的长度从 1 1 1递增到 n n n, m a p map map占的空间太大。
// 90pts
#include
#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 、 12 11、12 11、12这两个点。这两个点的条件上面也分析过了,“字符串中的每个字符独立等概率地从字符集中选择”, a − z a-z a−z这 26 26 26个字母出现的概率是相同的,那我们就在输入字符串之后统计一下每个字母出现的次数,如果该字母出现的次数与期望次数的差值过大,那么我们认为这个点不是第 11 11 11个点或第 12 12 12个点。
// 100pts
#include
#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;
}
}