C - Compress Words
题意:通过前后缀重叠来压缩句子。
法1: 字符串hash暴力匹配
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const ull mod=999999998, base=2333333;
int main(){
int n; cin>>n;
string a; cin>>a;
for(int i=2; i<=n; i++){
string b; cin>>b;
ull h1=0, h2=0, pos=0, t_base=1;
for(int i=1; i<=min(a.size(), b.size()); i++){
h1 = (h1*base+a[a.size()-i]) % mod;
h2 = (t_base*b[i-1]+h2) % mod;
if(h1 == h2) pos = i; //匹配上了,更新位置
t_base = (t_base*base) % mod; //b字符串的哈希系数(要不断增大)
}
a += b.substr(pos);
}
cout<<a;
}
法2:KMP的巧妙运用
注意这里的match函数,和一般的KMP匹配过程有两处不同。
- 匹配起点是max(len1-len2+1,1),保证了s串是后缀匹配
- t 串不需要全部匹配完成,返回的x是一个“匹配进度”,表示匹配到的前后缀的长度
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
int n, ne[maxn]; string s;
void get_ne(const string&s){
int len = s.size();
for(int i=2; i<=len; i++){
int x = ne[i-1];
while(x>0 && s[x] != s[i-1]) x=ne[x];
if(s[x]==s[i-1]) ne[i] = x+1;
else ne[i] = 0;
}
}
int match(const string& s, const string& t){ //返回前后缀“匹配进度”
int x = 0, len1=s.size(), len2=t.size();
for(int i=max(len1-len2+1, 1); i<=len1; i++){ //注意开始位置,保证了s后缀匹配,t前缀匹配
while(x>0 && t[x]!=s[i-1]) x=ne[x];
if(t[x]==s[i-1]) x++;
}
return x;
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>n; cin>>s;
string ans = s;
for(int i=2; i<=n; i++){
cin>>s; get_ne(s);
ans += s.substr(match(ans,s));
}
cout<<ans;
}
I-最长回文(manacher模板题)
原链接挂了,放个洛谷链接:【模板】manacher 算法 - 洛谷
#include <bits/stdc++.h>
using namespace std;
string s,t;
int p[23000000]; //要开到字符串长度两倍多
int manacher(const string& s){
t="@#"; //头标记+分隔符
for(char ch:s) {t+=ch; t+='#';} //加入字符和间隔符
t += '*'; //尾标记
// cout<<t<<endl;
int R=0, mid=0, res=0;
for(int i=1; i<t.size()-1; i++){
p[i] = i<R ? min(p[2*mid-i], R-i) : 1;
while(t[i+p[i]] == t[i-p[i]]) p[i]++;
if(i+p[i] > R) {R=i+p[i]; mid=i;}
res = max(res, p[i]-1);
}
return res;
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
while(cin>>s){
cout<<manacher(s)<<'\n';
}
}
J - Password
题意:给定一个字符串,找出最长的公共前,中,后缀
法1:用hash暴力匹配前后缀,然后用匹配出的前后缀逐个检查是否有对应的中缀(检查方法是KMP)
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 400010;
const ull mod=999999998, base=2333333;
string s; stack<int> stk;
bool KMP(string s, string t){
int len1 = s.size(), len2 = t.size();
int next[len2];
next[0] = 0;
int i=1, j=0; //后缀末尾,前缀末尾
for(; i<len2; i++){ //1.构建next数组
while(j>0 && t[j]!=t[i]) j=next[j-1]; //不匹配,回退
if(t[j]==t[i]) j++; //匹配上了,移动前缀末尾
next[i] = j; //记录next位置
}
i=0, j=0; //i和j分别为文本串和模式串的指针
for(; i<len1; i++){ //匹配过程
while(j>0 && t[j]!=s[i]) j=next[j-1]; //不匹配,回退
if(t[j]==s[i]) j++; //匹配,“匹配进度”+1
if(j==len2) return 1; //匹配完成
}
return 0; //匹配失败,找不到子串
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>s; int len = s.size();
ull h1=0, h2=0, pos=0, t_base=1;
for(int i=1; i<=len; i++){
h1 = (h1*base + s[len-i]) % mod;
h2 = (t_base*s[i-1] + h2) % mod;
if(h1==h2) stk.push(i);
t_base = (t_base*base) % mod;
}
while(stk.size()){
int L = stk.top(); stk.pop();
if(KMP(s.substr(1,s.size()-2), s.substr(0,L))) {cout<<s.substr(0,L); return 0;}
}
cout<<"Just a legend";
}
法2:利用next数组的性质匹配前后缀,然后再查找是否存在对应的中缀
(查找方法是不断缩短字符串,重复上述匹配前后缀的方法,检查是否有对应中缀)
要注意的是,next数组存的是最长的公共前后缀位置,要找到所有公共前后缀,要迭代令x = ne[x],直到找到或发现找不到。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
string s; int ne[maxn];
bool check(int a){ //检查s是否有长度为a的中缀与公共前后缀相等
int len = s.size();
for(int i=a+1; i<=len; i++){
int x = ne[i-1];
while(x>=a){
if(x==a) return 1;
else x = ne[x];
}
}
return 0;
}
void get_ne(const string& s){
int len = s.size();
for(int i=2; i<=len; i++){
int x = ne[i-1]; //上一个字母匹配到的位置
while(x>0 && s[x]!=s[i-1]) x = ne[x]; //匹配失败,回退
if(s[x]==s[i-1]) ne[i] = x+1; //得配
else ne[i] = 0; //失配
}
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>s; int len = s.size();
get_ne(s);
int x = ne[len]; //最长公共前后缀长度
while(x>0){
if(check(x)) {cout<<s.substr(0,x); return 0;}
else x=ne[x];
}
cout<<"Just a legend";
}
K - Camp Schedule
题意:给定固定数量0和1的 s 串,用 s 串凑出含有尽可能多 t 串的字符串。
解法:凑成 t + n * t的尾缀的形式。注意特判t不存在尾缀的情况(即没有公共前后缀的情况)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
int ne[maxn];
void get_ne(string t){
int len=t.size();
ne[0]=0;
int i=1, j=0;
for(; i<len; i++){
while(j>0 && t[j]!=t[i]) j=ne[j-1];
if(t[j]==t[i]) j++;
ne[i] = j;
}
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
string s, t; cin>>s>>t;
get_ne(t); int p = ne[t.size()-1];
string t_sub = t.substr(p); //t的尾缀
int have0=0, have1=0, n0=0,n1=0, nn0=0,nn1=0;
for(char ch : s) {if(ch=='0') have0++; else have1++;}
for(char ch : t) {if(ch=='0') n0++; else n1++;}
for(char ch : t_sub) {if(ch=='0') nn0++; else nn1++;}
//先凑一个打头的t串
string ans = t; have0-=n0, have1-=n1;
if(have0<0 || have1<0) {cout<<s; return 0;} //凑不出,说明不能含t,直接输出即可
if(nn0==0 && nn1==0) {t_sub=t; nn0=n0; nn1=n1;} //t没有尾缀的时候特判,把整个t作为尾缀
//然后后面放尽可能多的t尾缀
while(have0>=nn0 && have1>=nn1){
have0-=nn0, have1-=nn1;
ans += t_sub;
}
//凑不出尾缀了,后面随便放
while(have0-- > 0) ans += '0';
while(have1-- > 0) ans += '1';
cout<<ans;
}
L - Reverse String
题意:在s串上选一个位置开始,往右读几个字符,再往左读几个字符,能否恰好读出来是t串?
解法:注意只能转向一次,且是一开始往右,后来往左。看到字符串最长只有500,直接暴力dfs即可,注意要有一个变量用来记录是否变向过。
#include <bits/stdc++.h>
using namespace std;
int n; string s, t; bool found;
void dfs(int cur, int p, int rev){
if(found) return; //如果找到了,就不用再dfs了
if(p==t.size()-1) {found=1; return;} //找到了
if(cur+1<s.size() && s[cur+1]==t[p+1] && rev==1) dfs(cur+1,p+1,1); //不转向,继续向右
if(cur-1>=0 && s[cur-1]==t[p+1]) dfs(cur-1, p+1, 0); //转向左或者继续向左
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>n;
while(n--){
found=0;
cin>>s>>t;
for(int i=0; i<s.size(); i++){
if(found) break; //剪枝
if(s[i]==t[0]) dfs(i,0,1);
}
if(found) cout<<"YES"<<'\n';
else cout<<"NO"<<'\n';
}
}