目录
题单链接传送门
kmp算法,最核心的就是理解pmt数组,pmt数组前缀后缀相等的最大长度,那么可以理解为记录为上次匹配的位置,所以每次递归的长度是j-pmt[j-1];其次就是pmt可以判断是否有循环节(n%(n−pmt[n-1])==0 && pmt[n-1]),这里的n-pmt[n-1]是最小的循环节,那么循环次数就是n/(n-pmt[n-1]).
原题链接传送门
kmp模板题,要注意的是这里是数字,不能让它们转化为字符串再去求(这里没仔细读题wa了很多发),直接按数组去求就好了。
- #include
- #include
- #include
- #include
- #include
- #define int long long
- using namespace std;
-
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N];
- int s[N], p[N];
-
- signed main()
- {
- ios::sync_with_stdio(false);
- cin.tie(nullptr);
- int t;
- std::cin >> t;
- while (t--)
- {
- int n, m;
- cin >> n >> m;
- for (int i = 0; i < n; i++) cin >> s[i];
- for (int j = 0; j < m; j++) cin >> p[j];
- for (int i = 1, j = 0; i < m; i++)
- {
- while (j && p[i] != p[j]) j = pmt[j - 1];
- if (p[i] == p[j]) j++;
- pmt[i] = j;
- }
- bool flag = 0;
- for (int i = 0, j = 0; i < n; i++)
- {
- while (j && s[i] != p[j]) j = pmt[j - 1];
- if (s[i] == p[j]) j++;
- if (j == m){
- flag = true;
- cout << i - j + 2 << "\n";
- break;
- }
- }
- if(!flag) cout << -1 << "\n";
- }
- return 0;
- }
原题链接传送门
模式串在匹配串中出现的次数,模板题。
- #include
- #include
- #include
- #include
- #include
- #define int long long
- using namespace std;
-
- const int N = 1e6 + 9;
- int pmt[N];
-
- void get_pmt(const string& p) {
- for(int i = 1, j = 0; i < p.size(); i++)
- {
- while(j && p[i] != p[j]) j = pmt[j - 1];
- if(p[i] == p[j]) j ++;
- pmt[i] = j;
- }
- }
-
- int kmp(const string& s, const string& p) {
- int ans = 0;
- for(int i = 0, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != p[j]) j = pmt[j - 1];
- if(s[i] == p[j]) j ++;
- if(j == p.size()){
- ans++;
- j = pmt[j - 1];
- }
- }
- return ans;
- }
-
- signed main()
- {
- ios::sync_with_stdio(false);
- cin.tie(nullptr);
- int t;
- std::cin >> t;
- while(t--){
- string p, s;
- cin >> p >> s;
- get_pmt(p);
- cout << kmp(s, p) << endl;
- }
- return 0;
- }
原题链接传送门
一个字符串能分成相同的几段,这里要注意由于不能有重叠部分所以在递归匹配时重新从0开始即可。
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- const int N = 1e6 + 9;
- int pmt[N];
-
- void get_pmt(const string& s) {
- for(int i = 1, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- }
-
- int kmp(const string& s, const string& p) {
- int ans = 0;
- for(int i = 0, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != p[j]) j = pmt[j - 1];
- if(s[i] == p[j]) j ++;
- if(j == p.size())
- {
- ans++;
- // j = pmt[j - 1];
- j = 0;
- }
- }
- return ans;
- }
-
- int main()
- {
- ios::sync_with_stdio(false);
- cin.tie(0);
- string s, p;
- while(cin >> s)
- {
- if(s == "#") break;
- cin >> p;
- get_pmt(s);
- int m = p.size();
- cout << kmp(s, p) << endl;
- }
- return 0;
- }
原题链接传送门
题意:一个字符串最少加多少个字符能使字符串变成周期循环.
思路:最小循环节为n-pmt[n-1],所以加的最小长度就是n-pmt[n-1]-pmt[n-1]%(n-pmt[n-1]).
- #include
- using namespace std;
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N];
- char s[N];
-
- signed main()
- {
- int t;
- scanf("%d",&t);
- while(t--) {
- scanf("%s",s);
- int n = strlen(s);
- for(int i = 1, j = 0; i < n; i++){
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- int l = pmt[n - 1], r = n - l;
- if(l && l % r == 0) cout << 0 << endl;
- else cout << r - l % r << endl;
- }
- return 0;
- }
原题链接传送门
一个字符串,问在所有的[0, i]区间是否有完整的循环节,同样利用了pmt对循环节的求解。
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N];
-
- signed main()
- {
- ios::sync_with_stdio(false);
- cin.tie(nullptr);
- int n;
- int test = 0;
- while(cin >> n && n != 0) {
- string s;
- cin >> s;
- for(int i = 1, j = 0; i < n; i++){
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- cout << "Test case #" << ++test << "\n";
- for(int i = 0; i < n; i++){
- int j = i + 1;
- int k = j - pmt[i];
- if(j % k == 0 && pmt[i])
- cout << j << " " << j / k << "\n";
- }
- cout << "\n";
- }
- return 0;
- }
原题链接传送门
题意:假设s可以由t重复k次拼成,即s=tttt……tt,我们称为s=t^k,k是s的幂次。先给定一个字符串s,求最大的幂次n使得存在t满足s=t^n。先看看是不是周期循环,否输出1是输出循环次数即可。
- #include
- #include
- #include
- #include
- using namespace std;
-
- const int N = 1e6 + 9;
- int pmt[N];
-
- void get_pmt(const string& s) {
- for(int i = 1, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- }
-
- int main()
- {
- ios::sync_with_stdio(false);
- cin.tie(0);
- string s;
- while(cin >> s)
- {
- if(s == ".") break;
- get_pmt(s);
- int n = s.size();
- int m = n - 1;
- if(n % (n - pmt[m])) std::cout << "1\n";
- else std::cout << n / (n - pmt[m]) << "\n";
- }
- return 0;
- }
原题链接传送门
题意:在每个字符串中求出所有既是前缀又是后缀的子串长度。
思路:pmt表示前缀与后缀相等的最大长度,递归pmt即可。
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N], ans[N];
- char s[N];
-
- signed main()
- {
- while(~scanf("%s",s)) {
- int n = strlen(s);
- for(int i = 1, j = 0; i < n; i++){
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- int cnt = 0;
- for(int j = n; j; j = pmt[j - 1]) ans[++cnt] = j;
- for(int i = cnt; i; i--) cout << ans[i] << " \n"[i == 1];
- }
- return 0;
- }
原题链接传送门
多个字符串的公共子序列,水题暴力枚举即可。
- #include
- #include
- #include
- signed main(){
- int T, n;
- std::cin >> T;
- while (T--){
- std::cin >> n;
- std::string s, t;
- std::vector
str; - std::set
ans; - for (int i = 0; i < n; i++)
- std::cin >> s, str.push_back(s);
- bool f, fy = 0;
- s = str[0];
- for (int i = 60; i >= 3; i--){
- for (int j = 0; i + j <= 60; j++){
- f = 1, t = s.substr(j, i);
- for (int k = 1; k < str.size(); k++)
- if ((int)str[k].find(t) == -1){
- f = 0;
- break;
- }
- if (f) ans.insert(t);
- }
- if (!ans.empty()){
- fy = 1;
- break;
- }
- }
- std::cout << ((fy) ? (*(ans.begin())) : "no significant commonalities");
- std::cout << "\n";
- }
- return 0;
- }
原题链接传送门
题意:两个字符串s,t,输出同时作为s前缀和t后缀的最长字符串,然后输出该字符串的长度
思路:根据题意,我们可以选择将两个字符拼在一起,最后找到长度不大于s或t的任意一个既是前缀又是后缀的字符串即可。
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define int long long
- using namespace std;
-
- const int mod = 10007;
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N];
-
- void get_pmt(const string& s) {
- for(int i = 1, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- }
-
- signed main()
- {
- ios::sync_with_stdio(false);
- cin.tie(nullptr);
- string s, p;
- while(cin >> s >> p) {
- int lens = s.size(), lenp = p.size();
- s = s + p;
- get_pmt(s);
- n = s.size();
- int ans = pmt[n - 1];
- while(ans > lens || ans > lenp) ans = pmt[ans];
- if(ans==0) {cout<<0<
continue;} - cout << s.substr(0, ans) << " " << ans << endl;
- }
- return 0;
- }
-
原题链接传送门
题意:给定一个字符串s,求s的每个前缀在此字符串中出现的次数,然后对次数求和,然后再对10007取模,就是要输出的答案。
思路:主要考察对pmt数组的理解,每个前缀至少出现一次,如果i匹配失败,会回到pmt[i-1],所以当前的字符串包含pmt[i-1]包含的前缀和它本身。
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define int long long
- using namespace std;
-
- const int mod = 10007;
- const int N = 1e6 + 9;
- int n, m;
- int pmt[N];
- string s;
-
- void get_pmt(const string& s) {
- for(int i = 1, j = 0; i < s.size(); i++)
- {
- while(j && s[i] != s[j]) j = pmt[j - 1];
- if(s[i] == s[j]) j ++;
- pmt[i] = j;
- }
- }
-
- signed main()
- {
- ios::sync_with_stdio(false);
- cin.tie(nullptr);
- int t;
- cin >> t;
- while(t--) {
- cin >> n >> s;
- get_pmt(s);
- int ans = 0;
- for(int i = 0; i < n; i++) if(pmt[i]) ans = (ans + 1) % mod;
- cout << (ans + n) % mod << "\n";
- }
- return 0;
- }
2022/11/5 0:45补11.3-11.4