洛谷CF1056E
前言
这是本蒟蒻第一篇题解,有什么不足之处欢迎各位大佬指出。
题目大意
给定一个仅包含 \(\tt 0\) 和 \(\tt 1\) 的字符串 \(s\) 与一个仅包含英文小写字母的字符串 \(t\) ,给 \(s\) 中的每个 \(\tt 0\) 一个映射字符串 \(r_0\) 与每个 \(\tt 1\) 一个映射字符串 \(r_1\),\(r_0\) 与 \(r_1\) 按照 \(s\) 中 \(\tt 0\) 和 \(\tt 1\) 的排列顺序进行排列所得到的字符串与 \(t\) 相同且不相等(注意审题),求一共存在多少组 \(r_0\) 与 \(r_1\) 满足要求。保证 \(s\) 中至少有一个 \(\tt 0\) 和 \(\tt 1\) ,\(2 \le \lvert s \rvert \le 10^5,1 \le \lvert t \rvert \le 10^6\)。
思路
考虑枚举。首先枚举 \(r_0\)、\(r_1\) 的长度 \(len_0\)、\(len_1\) 与哈希值 \(h_0\)、\(h_1\),接着逐个检验 \(h_0\)、\(h_1\) 与 \(t\) 的相应子串哈希值是否匹配,如果不匹配结束检验,如果全部匹配则总情况数加一,接着继续往后枚举。
设 \(s\) 与 \(t\) 的长度分别为 \(n\) 与 \(m\),\(s\) 中 \(\tt 0\) 与 \(\tt 1\) 的数量分别为 \(n_0\)、\(n_1\)。\(len_0\) 与 \(len_1\) 满足以下关系:
将上式移项得:
枚举的临界状态分别为 \(len_0=1\),\(len_1=1\),即 \(r_0\)、\(r_1\) 分别仅有一个字符组成时的状态。我们从 \(len_0=1\) 开始枚举,则枚举的终点是 \(len_0=\dfrac{m-n_1}{n_0}\)。依据题意,\(len_0\) 与 \(len_1\) 必须为正整数,那么当 \(m-n_0 \cdot len_0\) 不能被 \(n_1\) 整除时 \(len_1\) 就不可能是整数,可以直接跳过这一种情况接着往后枚举。
当 \(len_0\) 与 \(len_1\) 均为整数时,我们需要得到 \(h_0\) 与 \(h_1\)。如何得到 \(h_0\) 与 \(h_1\) 呢?我们定义变量 \(peroid\) 代表 \(s\) 从第一个字符开始有多少个连续的字符与 \(s_1\) 相同,比如对字符串“\(\texttt{00010}\)”,\(peroid=3\);对字符串“\(\texttt{1011}\)”,\(peroid=1\)。求出 \(peroid\) 之后,我们开始枚举 \(r_0\) 与 \(r_1\) 。
若 \(s\) 的第一个字符为 \(\tt 0\),那么 \(r_0\) 的第一个字符就是 \(t_1\),最后一个字符就是 \(t_{len_0}\),\(h_0\) 的值即为 \(t\) 从第一个字符到第 \(len_0\) 个字符所组成的子串的哈希值。那么 \(h_1\) 呢?这里就需要用到我们刚才求出来的 \(peroid\),\(t\) 从第一个字符算起,有 \(peroid\) 个连续的 \(r_0\),每个 \(r_0\) 的长度为 \(len_0\),那么 \(t\) 中第一个 \(r_1\) 的第一个字符就是 \(t_{len_0 \times peroid+1}\),最后一个字符就 \(t_{len_0 \times peroid+len_1}\),\(h_1\) 的值也就是上述子串的哈希值。
若 \(s\) 的第一个字符为 \(\tt 1\),过程与上述类似,这里不再赘述。
最后是检查阶段。我们逐个检查 \(s\) 中的字符是否对应 \(t\) 中的相应子串。维护一个指针 \(pos\) ,指向当前所检测子串的第一个字符。若当前子串为 \(r_0\),比较 \(t_{pos}\) 到 \(t_{pos+len_0-1}\) 的哈希值与 \(h_0\);若当前子串为 \(r_1\),比较 \(t_{pos}\) 到 \(t_{pos+len_1-1}\) 的哈希值与 \(h_1\),若二者相等,接着往后比较,反之终止检查,继续往后枚举。
AC Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
const int BASE = 13331, MOD = 1e9 + 7;
int n, m, n0 = 0, n1 = 0;
long long Hash[MAXN], Po[MAXN];//十年OI一场空,不开long long见祖宗
char s[MAXN], t[MAXN];
long long get_hash(int l, int r) // 获取子串哈希值
{
if (l < 1 || r > m) return -1; // 边界检查
return ((Hash[r] - Hash[l-1] * Po[r-l+1] % MOD) + MOD) % MOD;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> (s + 1);
cin >> (t + 1);
n = strlen(s + 1);
m = strlen(t + 1);
// 预处理哈希值和幂
Po[0] = 1;
for (int i = 1; i <= m; i++)
{
Hash[i] = (Hash[i-1] * BASE + (t[i] - 'a' + 1)) % MOD;
Po[i] = Po[i-1] * BASE % MOD;
}
// 统计0和1的数量
for (int i = 1; i <= n; i++)
{
if (s[i] == '0') n0++;
else n1++;
}
int period = 1;
// 计算连续相同字符的数量
for (int i = 2; i <= n; i++)
{
if (s[i] == s[1]) period++;
else break;
}
int ans = 0;
// 枚举0对应的字符串长度
for (int len0 = 1; len0 * n0 < m; len0++)
{
if ((m - len0 * n0) % n1 != 0) continue;
int len1 = (m - len0 * n0) / n1;
if (len1 <= 0) continue;
long long h0, h1;
bool valid = true;
// 根据首字符类型确定h0和h1
if (s[1] == '0')
{
if (1 + len0 - 1 > m) continue;
h0 = get_hash(1, len0);
int start = 1 + period * len0;
int end = start + len1 - 1;
if (end > m) continue;
h1 = get_hash(start, end);
} else
{
if (1 + len1 - 1 > m) continue;
h1 = get_hash(1, len1);
int start = 1 + period * len1;
int end = start + len0 - 1;
if (end > m) continue;
h0 = get_hash(start, end);
}
if (h0 == h1) continue;// 依题意,两个字符串必须不同
// 验证整个字符串
int pos = 1;
for (int i = 1; i <= n && valid; i++)
{
if (s[i] == '0')
{
if (pos + len0 - 1 > m)
{
valid = false;
break;
}
if (get_hash(pos, pos + len0 - 1) != h0)
{
valid = false;
break;
}
pos += len0;
} else
{
if (pos + len1 - 1 > m)
{
valid = false;
break;
}
if (get_hash(pos, pos + len1 - 1) != h1)
{
valid = false;
break;
}
pos += len1;
}
}
if (valid && pos == m + 1)ans++;
}
cout << ans << endl;
return 0;
}
完结撒花
浙公网安备 33010602011771号