2022SCUT Winter Training Day 4 记录(字符串专题)

C - Compress Words

Problem - 1200E - Codeforces

题意:通过前后缀重叠来压缩句子。

法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匹配过程有两处不同。

  1. 匹配起点是max(len1-len2+1,1),保证了s串是后缀匹配
  2.  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

Problem - 126B - Codeforcest

题意:给定一个字符串,找出最长的公共前,中,后缀

法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

Problem - 1137B - Codeforces

题意:给定固定数量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

Problem - 1553B - Codeforces

题意:在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';
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值