非常非常长…一堆破题目还是折腾了挺久的…本来我就弱…字符串部分更加是弱QAQ…做起来非常不顺利qwq ==
A.HDU3374
题目其实很简单,就是求给定串的最大最小表示法,然后求出现的次数的话,就把原串复制一遍接在串尾,然后跑一遍kmp求出现次数即可。
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#include<cstring>
using namespace std;
int minpos(string str,int len)
{
int i=0,j=1,k=0,t;
while(i<len&&j<len&&k<len){
t=str[(i+k)>=len?i+k-len:i+k]-str[(j+k)>=len?j+k-len:j+k];
if(!t)k++;
else{
if(t>0)i=i+k+1;
else j=j+k+1;
if(i==j){
++j;
}
k=0;
}
}
return min(i,j);
}
int maxpos(string str,int len)
{
int i=0,j=1,k=0,t;
while(i<len&&j<len&&k<len){
t=str[(i+k)>=len?i+k-len:i+k]-str[(j+k)>=len?j+k-len:j+k];
if(!t)k++;
else{
if(t<0)i=i+k+1;
else j=j+k+1;
if(i==j){
++j;
}
k=0;
}
}
return min(i,j);
}
int next1[1000005];
void getnext(string str)
{
next1[0]=-1;
int k=-1,j=0;
while(j<str.size()-1){
if(k==-1||str[k]==str[j]){
k++;j++;
next1[j]=k;
}
else{
k=next1[k];
}
}
}
int main()
{
string str;int i,j,len;
while(cin>>str){
int len=str.size();
int pos1=minpos(str,len),pos2=maxpos(str,len);
str+=str;string str2=str.substr(pos1,len),str3=str.substr(pos2,len);str=str.substr(0,str.size()-1);//一定要减掉最末位,否则会出错
memset(next1,0,sizeof(next1));getnext(str2);
i=0,j=0;int cnt1=0,cnt2=0;
while(i<str.size()){//因为是输出s1中所有s2的位置所以while里只对s1的长度做出限制
if(j==-1||str[i]==str2[j]){
if(j==str2.size()-1){//判断一定要放在这里,否则i++,j++会导致重叠部分无法利用,比如ABABABAB和ABAB的匹配
cnt1++;j=next1[j];continue;
}
i++;j++;
}
else{
while(j>=0&&str2[j]!=str[i])//这里注意要加等于号
j=next1[j];
}
}
memset(next1,0,sizeof(next1));getnext(str3);
i=0,j=0;
while(i<str.size()){
if(j==-1||str[i]==str3[j]){
if(j==str3.size()-1){
cnt2++;j=next1[j];continue;
}
i++;j++;
}
else{
while(j>=0&&str3[j]!=str[i])//这里注意要加等于号
j=next1[j];
}
}
cout<<pos1+1<<' '<<cnt1<<' '<<pos2+1<<' '<<cnt2<<endl;
}
return 0;
}
B.HDU1880
其实就是求一堆字符串的对应关系,很容易就会想到偷懒用map搞一搞,然而会mle…==
就算改成map里一个int一个string依然会mle==
于是只好把所有输入的字符拿string数组存起来,然后计算他们的hash值,把hash值映射为string数组的下标…然而由于hash过于玄学…因此wa了n次…说好的综合性能最好的hash呢…只好换了一个
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include <map>
using namespace std;
unsigned int ELFHash(string str)
{
// unsigned int hash = 0;
// unsigned int x = 0;
//
// for(int i=0;i<str.size();i++)
// {
// hash = (hash << 4) + (str[i]);
// if ((x = hash & 0xF0000000L) != 0)
// {
// hash ^= (x >> 24);
// hash &= ~x;
// }
// }
//
// return (hash & 0x7FFFFFFF);
unsigned int seed = 131;
unsigned int h = 0;
for (int i=0;i<str.size();i++) {
h = h * seed + (str[i]);
}
return (h & 0x7FFFFFFF);
}
int main()
{
string str,str1[100005],str2[100005];
//unsigned int hash1[100005],hash2[100005];
map<int,int>mp1,mp2;int i=0;
while(cin>>str1[++i]&&str1[i]!="@END@"){
getchar();getline(cin,str2[i]);
unsigned int hash1=ELFHash(str1[i]),hash2=ELFHash(str2[i]);
mp1[hash1]=i;mp2[hash2]=i;
}
int n;cin>>n;getchar();
while(n--){
getline(cin,str);
if(str[0]=='['){
unsigned int hash1=ELFHash(str);
int pos=mp1[hash1];
if(pos==0){
puts("what?");
}
else{
cout<<str2[pos]<<endl;
}
}
else{
unsigned int hash1=ELFHash(str);
int pos=mp2[hash1];
if(pos==0){
puts("what?");
}
else{
str=str1[pos].substr(1,str1[pos].size()-2);
cout<<str<<endl;
}
}
}
return 0;
}
C.HDU1251
裸的trie,把tag改成cnt然后插入单词的时候累加一下就行,查询的时候输出对应点的cnt即可。
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
struct node{
char c;
vector<node*>s;//其实也可以开一个26个节点的数组,那样比较节省查找时间,但是浪费空间
bool tag,ask;//因为到达某个节点后即使完全匹配,也可能只是另一个前缀相同的单词的一部分,
// 所以需要一个标记来标记这个节点是否是某单词的结尾
int cnt;
node(){//新建函数
tag=false;ask=false;s.clear();cnt=0;
}
}*root=new node();
void add(string str)
{
int i,j;
struct node*p=root;
for(i=0;i<str.size();i++){
for(j=0;j<p->s.size();j++){
if(p->s[j]->c==str[i])break;
}
if(j==p->s.size()){//如果找不到
node*t=new node();t->c=str[i];
p->s.push_back(t);p=t;
}
else
p=p->s[j];
p->cnt++;
}
p->tag=true;//单词结尾所在节点标记
}
int query(string str)
{
int i,j;node*p=root;
for(i=0;i<str.size();i++){
for(j=0;j<p->s.size();j++){
if(p->s[j]->c==str[i])break;
}
if(j==p->s.size())return 0;
p=p->s[j];
}
return p->cnt;
}
int main()
{
string str;
while(getline(cin,str)&&str.end()!=str.begin()){
add(str);
}
while(cin>>str){
cout<<query(str)<<endl;
}
return 0;
}
D.HihoCoder - 1032
Manacher模板,实在没啥好说的。
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1100000;
char str1[maxn];//原字符串
char tmp1[maxn<<1];//转换后的字符串
int Len1[maxn<<1];
//转换原始串
int INIT(char *st)
{
int i,len=strlen(st);
tmp1[0]='@';//字符串开头增加一个特殊字符,防止越界
for(i=1;i<=2*len;i+=2)
{
tmp1[i]='#';
tmp1[i+1]=st[i/2];
}
tmp1[2*len+1]='#';
tmp1[2*len+2]='$';//字符串结尾加一个字符,防止越界
tmp1[2*len+3]=0;
return 2*len+1;//返回转换字符串的长度
}
//Manacher算法计算过程
int MANACHER(char *st,int len)
{
int mx=0,ans=0,po=0;//mx即为当前计算回文串最右边字符的最大值
for(int i=1;i<=len;i++)
{
if(mx>i)
Len1[i]=min(mx-i,Len1[2*po-i]);//在Len[j]和mx-i中取个小
else
Len1[i]=1;//如果i>=mx,要从头开始匹配
while(st[i-Len1[i]]==st[i+Len1[i]])
Len1[i]++;
if(Len1[i]+i>mx)//若新计算的回文串右端点位置大于mx,要更新po和mx的值
{
mx=Len1[i]+i;
po=i;
}
ans=max(ans,Len1[i]);
}
return ans-1;//返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
int main()
{
int n;cin>>n;
while(n--) {
cin >> str1;memset(Len1,0,sizeof(Len1));
int len = INIT(str1);
cout << MANACHER(tmp1, len) << endl;
}
return 0;
}
E.HDU3746
用kmp的next数组求最小循环节即可。
#include<cstdio>
#include<iostream>
#include<cstring>
//#include<string>
using namespace std;
int next1[100005];
char str[100005];
void getnext(void)
{
next1[0]=-1;
int k=-1,j=0;
int len=strlen(str);
while(j<len){
if(k==-1||str[k]==str[j]){
k++;j++;
next1[j]=k;
}
else{
k=next1[k];
}
}
}
int main()
{
int t,i,j;
cin>>t;getchar();
while(t--){
scanf("%s",str);int len1=strlen(str);
getnext();
//for(i=1;i<=str.size();i++)cout<<next1[i]<<endl;
int len=len1-next1[len1];
int ans=(len-len1%len);
if(len1>len)ans%=len;
cout<<ans<<endl;
}
return 0;
}
F.HDU6153
题意
给出两个字符串 A 和 B ,求 B 的所有后缀在 A 中的出现次数与后缀长度的乘积和。
思路
枚举所有后缀显然不是理想的方案,但是我们把两个串倒过来,问题就变成了求 B 的所有前缀在 A 中出现次数与前缀长度的乘积和。
于是便是 扩展 KMP 的基础题目啦。
对于 extend[i] 所保存的 y[i..n-1] 与 x[0..m-1] 的最长公共前缀。
我们可以枚举所有的 i ,对于每一个 i ,结果便是 1+2+3+…+extend[i] ,即 extend[i]×(extend[i]+1)2 。
累计求和即可。
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
/* 求解T中next[],注释参考GetExtend() */
const int mod=1e9+7;
void GetNext(string T, int next1[])
{
int t_len = T.size();
next1[0] = t_len;
int a;
int p;
for (int i = 1, j = -1; i < t_len; i++, j--)
{
if (j < 0 || i + next1[i - a] >= p)
{
if (j < 0)
p = i, j = 0;
while (p < t_len&&T[p] == T[j])
p++, j++;
next1[i] = j;
a = i;
}
else
next1[i] = next1[i - a];
}
}
//题定义:给定两个字符串S和T(长度分别为n和m),下标从0开始,定义extend[i]等于S[i]…S[n-1]与T的最长公共前缀的长度,求出所有的extend[i]。
/* 求解extend[] */
void GetExtend(string S, string T, int extend[], int next[])
{
GetNext(T, next); //得到next
int a;
int p; //记录匹配成功的字符的最远位置p,及起始位置a
int s_len = S.size();
int t_len = T.size();
for (int i = 0, j = -1; i < s_len; i++, j--) //j即等于p与i的距离,其作用是判断i是否大于p(如果j<0,则i大于p)
{
if (j < 0 || i + next[i - a] >= p) //i大于p(其实j最小只可以到-1,j<0的写法方便读者理解程序),
{ //或者可以继续比较(之所以使用大于等于而不用等于也是为了方便读者理解程序)
if (j < 0)
p = i, j = 0; //如果i大于p
while (p < s_len&&j < t_len&&S[p] == T[j])
p++, j++;
extend[i] = j;
a = i;
}
else
extend[i] = next[i - a];
}
}
int main()
{
int t,i,j;cin>>t;getchar();
while(t--) {
string s1,s2;
int next1[1000005] = {0};
int extend[1000005] = {0};
getline(cin,s1);getline(cin,s2);
reverse(s1.begin(),s1.end());reverse(s2.begin(),s2.end());
//GetNext(s1,next1);
GetExtend(s1,s2,extend,next1);
long long ans=0;
for(i=0;i<s1.size();i++){
ans+=(long long)(extend[i]+1)*(long long)extend[i]/2;ans%=mod;
}
cout<<ans<<endl;
}
return 0;
}
G.CodeForces - 4C
水题,拿map查重顺便统计一下该加哪个后标就行
#include<cstdio>
#include<iostream>
#include<set>
#include<algorithm>
#include<cstring>
#include<string>
#include <unordered_set>
#include<map>
using namespace std;
int main()
{
int n,i,j;
cin>>n;string str;
map<string,int>mp;
while(n--){
cin>>str;i=mp[str];
if(!i){
puts("OK");mp[str]++;
}
else{
mp[str]++;string str1;
while(i){
str1+='0'+i%10;i/=10;
}
reverse(str1.begin(),str1.end());cout<<str+str1<<endl;
}
}
return 0;
}
H.CodeForces - 471D
相当有意思的一个题。我们会发现,其实要想在长的墙中看到目标墙,其实就是希望在长的墙中有连续的一段,其中相邻两项的差与目标墙相同。那么我们在读入高度的时候只保存相邻项的差即可。然后注意到其实墙的起点可以随意弄到想要的高度,因此我们kmp匹配的时候从起点后面的一个点开始匹配即可。
注意n或者w等于1的时候要特判。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
using namespace std;
int next1[200005];
void getnext(vector<int>str)
{
next1[0]=-1;
int k=-1,j=0;
while(j<str.size()-1){
if(k==-1||str[k]==str[j]){
k++;j++;
next1[j]=k;
}
else{
k=next1[k];
}
}
return;
}
int main() {
int n, w, i, j, k;
vector<int> str1, str2;
//cin>>str1>>str2;
cin >> n >> w;
int num[200005];cin>>num[0];
for(i=1;i<n;i++){
scanf("%d",&num[i]);str1.push_back(num[i]-num[i-1]);
}cin>>num[0];
for(i=1;i<w;i++){
scanf("%d",&num[i]);str2.push_back(num[i]-num[i-1]);
}
if (n == 1) {
if (w <= 1) {
cout<<1<<endl;
}
else{
cout<<0<<endl;
}
return 0;
}
if(w==1){
cout<<n<<endl;return 0;
}
getnext(str2);
i = 0, j = 0;int ans=0;
while (i < str1.size()) {
if (j == -1 || str1[i] == str2[j]) {
if (j == str2.size() - 1) {//判断一定要放在这里,否则i++,j++会导致重叠部分无法利用,比如ABABABAB和ABAB的匹配
//cout << i - str2.size() + 1 << endl;
ans++;j = next1[j];
continue;
}
i++;j++;
}
// else if(j==0){
// if(str2[1]-str2[0]==str)
// }
else {
while (j >= 0 && str2[j] != str1[i])//这里注意要加等于号
j = next1[j];
}
}
cout<<ans<<endl;
return 0;
}
I.HDU4333
题意:给出一个不含前导0的数字,每一次把它的最后一位移动到最前面,一直持续下去,分别求形成的数字小于,等于和大于原来数的个数。
例如:134可以形成134,341,413三个数,所以分别是1,1,1。
如果移动过程中出现前导0,则去掉前导0,并认为形成了新的数字。
扩展KMP能求出一个串所有后缀串(即s[i…len])和模式串的最长公共前缀。于是只要将这个串复制一遍,求出该串每个后缀与其本身的最长公共前缀即可,当公共前缀>=len时,显然相等,否则只要比较下一位就能确定这个串与原串的大小关系。
至于重复串的问题,只有当这个串有循环节的时候才会产生重复串,用KMP的next数组求出最小循环节,用长度除以最小循环节得到循环节个数,在将3个答案都除以循环节个数即可。
是否有循环节的判断方法:
#include<iostream>
#include<string>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
void GetNext(string T, int next[])
{
int t_len = T.size();
next[0] = t_len;
int a;
int p;
for (int i = 1, j = -1; i < t_len; i++, j--)
{
if (j < 0 || i + next[i - a] >= p)
{
if (j < 0)
p = i, j = 0;
while (p < t_len&&T[p] == T[j])
p++, j++;
next[i] = j;
a = i;
}
else
next[i] = next[i - a];
}
}
int next1[N],next2[N*2];
void getnext(string str)
{
next1[0]=-1;
int k=-1,j=0;
while(j<str.size()){
if(k==-1||str[k]==str[j]){
k++;j++;
next1[j]=k;
}
else{
k=next1[k];
}
}
}
int main() {
int n, i, j;
cin >> n;int cnt=0;
getchar();
while (n--) {
cnt++;
string str1, str2;
getline(cin, str1);
int cntl=0,cnte=0,cntg=0;
str2 = str1 + str1;
getnext(str1);
GetNext(str2, next2);
int xh=0;
if(next1[str1.size()]&&str1.size()%(str1.size()-next1[str1.size()])==0) {
xh = str1.size() - next1[str1.size()];
xh = str1.size() / xh;
}
for(i=0;i<str1.size();i++){
if(next2[i]>=str1.size())cnte++;
else{
int len=next2[i];
if(str2[len]>str2[len+i])
cntl++;
else cntg++;
}
}
if(xh){
cntl/=xh;cnte/=xh;cntg/=xh;
}
printf("Case %d:",cnt);
cout<<" "<<cntl<<' '<<cnte<<' '<<cntg<<endl;
}
return 0;
}
J.CodeForces - 608E
太难了,本蒟蒻不会了QAQ ==
L.CodeForces - 282E
题意: 题中给出了包含 n 个数的一串数列a1, a2, …, an,规定前缀和 pre[ i ] 为前 i 个数的异或和,后缀和 suf[ i ] 为后 i 个数的异或和 (即 pre[ i ] =a1 xor a2 xor a3…….xor ai , suf[ i ]同理),现求一个前缀和 pre[ x ] 和后缀和 suf[ y ] 使得 pre [ x ] xor suf[ y ]最大,要求 x 小于 y ,即所选的前缀和后缀不能交叉
,前缀和后缀可以为空,此时值为0。
嘛…描述的过于繁琐了….
注意题上给的数据范围!
首先我们能想到裸的n^2做法,但显然是不行的,鉴于刚刚做了一个和这类似的题且正在做字典树专题,立马想到了字典树来做……….
既然超时是因为枚举两遍,那么我们可不可以只枚举一遍呢?
想到两个数间异或的运算是两个数转成二进制后的每一位相互比较,相同为0,不同为1,既然要求最长路径,那我们就可以每次贪心的每一位都去取和自己不同数,如果找不到那就只好取相同的数,(即假如 x 的第 i 位是 0 那么他最优选择就是第 i 位为 1 的数,如果找不到第 i 位为1的就只能找第 i 位为 0 的)优先考虑最高位,再到最低位,因为二进制数 1000 是肯定大于 0XXX 的,这样的话就问题就转到了怎么在一堆序列中找到一个特定的序列,就想到了用字典树来解决问题,本题的数据比较坑,超了 int 的范围,因此要用 long long 因为最大有10^12,大概是2的四十次方左右,但因为这只是单个数的值加以来后可能会更大,所以就选择2的50次方,那么就开一个有50层的字典树,每层有0和1两个儿子,字典树从上往下存高位到低位。
将后缀从前往后扫,每次相当于后缀和的取值减少了最前面的一位,而加入之前的前缀和异或上当前这一位并把这个值插入trie,意味着前缀和多了一种选择,那么在这种情况下找出当前状况下的最优解并尝试去更新答案即可。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int cnt=0;
typedef long long ll;
struct node {
int son[2];
void init() {
son[0] = son[1] = -1;
}
}ch[10000005];
void insert(ll x) {
for (int i = 63, point = 0; ~i; i--) {
int cur;
if ((1LL << i) & x)cur = 1;
else cur = 0;
if (ch[point].son[cur] == -1) {
ch[++cnt].init();
ch[point].son[cur] = cnt;
}
point = ch[point].son[cur];
}
}
ll find(ll x) {
ll res = 0;
for (int i = 63, point = 0; ~i; i--) {
int cur = ((1LL << i) & x) == 0;//注意1LL!
if (ch[point].son[cur] == -1)cur ^= 1;//如果这个当前位最优解不存在,只能取不优的解
res = (res << 1) + cur;
point = ch[point].son[cur];
}
return res;
}
ll num[100005],pre1=0,suf=0;
int main()
{
int n,i,j,k;
cin>>n;
for(i=1;i<=n;i++){
scanf("%lld",&num[i]);suf^=num[i];
}
ll ans=suf;ch[0].init();
for(i=0;i<=n;i++){
suf^=num[i];pre1^=num[i];//前缀和后缀和改变
insert(pre1);
ans=max(ans,suf^find(suf));
}
cout<<ans<<endl;
return 0;
}
M.CodeForces - 514C
题目做法与最后一题很类似,就是把原来的那些目标字符串先hash一波存进set里,然后询问的字符串读入后枚举每一位进行替换,看看替换后的hash值是否是set里存在的。注意替换后的字符串必须是与原串恰好有一位不同,也就是替换的字母一定要与原字母不同即可。然后hash的时候种子选131会冲突…选257就能过…玄学orz
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
const ll M=1000000007;
const int N=650000;
int main()
{
int n,m,i,j,k,t;ll seed[650000];
for(seed[0]=i=1;i<N;i++) seed[i]=seed[i-1]*257%M;
char str[650000];
set<ll>s;
cin>>n>>m;
for(i=1;i<=n;i++) {
scanf("%s",str);ll res=0;int len=strlen(str);
for(j=0;j<len;j++) res=(res*257+str[j])%M;
s.insert(res);
}
for(i=1;i<=m;i++){
scanf("%s",str);bool isok=false;
int len=strlen(str);ll res=0;
for(j=0;j<len;j++) res=(res*257+str[j])%M;
for(j=0;j<len;j++){
for(char c='a';c<='c';c++){
if(str[j]==c)continue;//注意替换后必须与原来位上的字母不同!!!
if(s.find((((c-str[j])*seed[len-j-1]+res)%M+M)%M)!=s.end()){
puts("YES");isok=true;break;
}
}
if(isok)break;
}
if(!isok)puts("NO");
}
return 0;
}
N.HYSBZ - 3670
NOI2014的原题…然而蒟蒻如我搞了很久才大概明白,先挖个坑。
#include <cstdio>
#include <cstring>
#include<iostream>
using namespace std;
const int MAXT = 5;
const int MAXN = 1000000;
const unsigned long long MOD = 1000000007;
int n, next1[MAXN + 1], num[MAXN + 1], num2[MAXN + 1];
char s[MAXN + 1];
int main() {
int t = 1;
scanf("%d", &t);
while (t--) {
scanf("%s", s);
n = strlen(s);
next1[0] = next1[1] = num[0] = num[1] = 0;
for (int i = 2, t = 0, k = 0; i <= n; i++) {
while (t && s[t] != s[i - 1]) t = next1[t];
while ((k && s[k] != s[i - 1]) || k >= i / 2) k = next1[k];
if (s[k] == s[i - 1]) num2[i] = num[++k] + 1;
else num2[i] = 0;
if (s[t] == s[i - 1]) next1[i] = ++t, num[i] = num[t] + 1;
else next1[i] = num[i] = 0;
}
unsigned long long ans = 1;
for (int i = 1; i <= n; i++) (ans *= (num2[i] + 1)) %= MOD;
printf("%llu\n", ans);
}
return 0;
}
O.HYSBZ - 3555
枚举每一个字符串,枚举该串上的每一位,计算该串减去这一位后的hash值并把它存起来。然后把所有存起来的hash值排一波序,看每个hash值相同的区间可以有多少对相似的字符串即可。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
unsigned long long hash1[6000010],seed[210],t;
char str[310];
int main()
{
int n,m,s,ans,cnt;
scanf("%d%d%d",&n,&m,&s);
int i,j;
for(seed[0]=i=1;i<m;i++) seed[i]=seed[i-1]*131;
for(i=1;i<=n;i++) {
scanf("%s", str);
for (t = j = 0; j < m; j++) t = t * 131 + str[j];
for (j = 0; j < m; j++) hash1[++cnt] = t - seed[m - j - 1] * str[j];
}
sort(hash1+1,hash1+cnt+1);
int segment=1;
for(i=2;i<=cnt;i++) {
if (hash1[i] == hash1[i - 1]) segment++;
else ans += segment * (segment - 1) / 2, segment = 1;
}
ans+=segment*(segment-1)/2;
cout<<ans<<endl;
return 0;
}