@LittleRewriter
2017-10-08T03:20:33.000000Z
字数 8136
阅读 934
字符串
事先声明,本Note并不是用来学习的。
因为后面的东西在下也没有听懂。
如果有什么建议的话欢迎指正吧。
hash就是一个字符串到整数的映射,可以快速比较字符串是否相等
比较整数是O(1)的,可以快速比较
hash相等的两个字符串大概率相等,不等的话一定不等(准确判否)
将字符串看做base进制大整数。
例如,小写字母可以看做一个27进制的数字
每一位乘进制+当前这位,同时进行取模。
hash[i]=(hash[i-1]*base+s[i])%p
base可以随便取,31 131 13131是比较好的。
并且最好不要前导0。
p最好是longlong范围内的一个素数,如1e18+9 23333333333333333
也可以unsinged long long 自动溢出,不过有可能被卡掉。
也可以多取几个素数,提高正确率。
用map维护即可
//以下所有运算都是在模大质数意义下进行。
首先取出hash[R]、hash[L-1]
然后在hash[L-1]对齐相减
hash[L,R]=hash[R]-hash[L-1]*pow(base,r-l+1)
由于幂只与区间长度有关,所以可以预处理。这样查询区间hash也是O(1)的。
知道B的长度L,在A后面补位
hash[A+B]=hash[A]*pow(base,L)%p+hash[B]
二分查找前缀的hash值,log复杂度
求出LCP比较后一位即可
用一个链表,O(1)查询
vector<ULL> v[1005];v[h%q].push_back(h);
hash[i]=(hash[i-1]*base^s[i])%p
无法利用前缀和性质,但可以避免被卡
给定一个字符串, 要求维护两种操作:在字符串中插入一个字符,询问某两个位置开始的最长公共前缀。插入操作<=200, 字符串长度<=5w, 查询操作<=2w。
插入字符直接重构即可
查询是裸LCP
下面的可以当做LCP模板
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int L,cc;char s[55005];char tmps[10];int c[55005];unsigned long long hash[55005];unsigned long long pow[55005];int main(){// freopen("data.in","r",stdin);pow[0]=1;for(int i=1;i<55005;i++) pow[i]=pow[i-1]*131;scanf("%s",s+1);cc=L=strlen(s+1);//s 1..Lfor(int i=1;i<=L;i++) {c[i]=c[i-1]+1;}for(int i=1;i<=L;i++){hash[i]=hash[i-1]*131+s[i]-'a'+1;}// add(2,1);// printf("%d %d\n",getpos(1),getpos(3));int n;scanf("%d",&n);while(n--){scanf("%s",tmps);/// putchar('#');// for(int i=1;i<=cc;i++){//// printf(" %d",c[i]);//// }// puts("");if(tmps[0]=='I'){scanf("%s",tmps);int pos;scanf("%d",&pos);if(pos>L){s[L+1]=tmps[0];hash[L+1]=hash[L]*131+s[L+1]-'a'+1;}else{for(int i=L;i>=pos;i--) s[i+1]=s[i];s[pos]=tmps[0];for(int i=pos;i<=L+1;i++) hash[i]=hash[i-1]*131+s[i]-'a'+1;int initpos=lower_bound(c+1,c+1+cc,pos)-c;for(int i=initpos;i<=cc;i++){c[i]++;}}L++;}else{int x,y;scanf("%d%d",&x,&y);x=c[x];y=c[y];int l=0,r=min(L-x,L-y)+2,mid;while(r-l>1){mid=(l+r)/2;if(hash[x+mid-1]-hash[x-1]*pow[mid]==hash[y+mid-1]-hash[y-1]*pow[mid]) l=mid;else r=mid;}printf("%d\n",l);}}return 0;}
N个长度为L的字符串,问有多少对只有一位不同。
对N个字符串求整串hash
然后不考虑某一类看有多少相同也就是删去某一位
用一个map来存储
#include<cstdio>#include<cstring>#include<algorithm>#include<map>using namespace std;int n,m,k;long long ans;typedef unsigned long long llu;char s[30005][205];llu hash[30005];map<llu,int> mp;llu pow[205];int main(){scanf("%d%d%d",&n,&m,&k);pow[0]=1;for(int i=1;i<=m;i++) pow[i]=pow[i-1]*13131;for(int i=0;i<n;i++){scanf("%s",s[i]);}for(int i=0;i<n;i++){for(int j=0;j<m;j++){hash[i]=hash[i]*13131+s[i][j];}}for(int p=0;p<m;p++){mp.clear();for(int i=0;i<n;i++){llu h=hash[i]-s[i][p]*pow[m-p-1];mp[h]++;}for(map< llu ,int>::iterator it=mp.begin();it!=mp.end();it++){if(it->second){ans+=it->second*(it->second-1)/2;}}}printf("%lld\n",ans);return 0;}
一位一位的匹配
O(nm)
//这个东西的原理由于在下水平有限真的说不清..所以实在不行的话就背代码吧
//hzk神犇讲的要好多了..ORZ!
用nxt数组表示最长的后缀=前缀位置
例如
序:12345678
字:abcababc
值:00012123
预处理nxt时,假设nxt[L]是处理完毕的。处理nxt[L+1]时,首先试图加一个元素上去,看能不能行,否则我们就不断的取nxt直到符合条件。
假设模式串[L,k]与主串[i,j]是可以匹配的,并且在j处不相等。现在模式串向后错一位,而我们现在要匹配[L+1],对应的是[i+1],由上可知[L+1,k]与[i+1,j]可匹配,因此我们通过nxt这个数组变换一下,就可以将匹配的后缀转化为前缀,跳过一部分继续匹配。
模板:
串为s,tint j=0;for(int i=2;i<=m;i++){while(j&&s[i]!=s[j+1]) j=nxt[j];if(s[i]==s[j+1]) j++;nxt[i]=j;}char t[]; nint j =0;for(int i=1;i<=n;i++){while(j && t[i]!=s[j+1]) j=nxt[j];if(t[i] == s[j+1]) j++;if(j==m){//...j=nxt[j];}}
由于后缀=前缀没有二分性质,只能递推求得。
所以不能hash。
长度为len-next[len]
如果l整除len-next[len]那么说明这个串可以完整被分成循环节。
否则就有l%(len-next[len])是除去循环节的部分。
求所有x使得串的长度为x的前缀和x的后缀相等。
不停取nxt即可。
#include<cstdio>#include<algorithm>#include<cstring>//by zrt//problem:using namespace std;char s[400005];int nxt[400005];int num[400005];int tot;int main(){#ifdef LOCALfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);#endifwhile(~scanf("%s",s)){int j=-1;nxt[0]=j;int l=strlen(s);for(int i=1;i<l;i++){while((~j)&&s[j+1]!=s[i]) j=nxt[j];if(s[i]==s[j+1]) j++;nxt[i]=j;}tot=0;for(int i=l-1;~i;i=nxt[i])num[++tot]=i+1;for(int i=tot;i;i--){printf("%d ",num[i]);}putchar('\n');}return 0;}
求num数组一一对于字符串S的前i个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作num[i]。
卡着不重叠的界来递推,一旦超范围就强制搞回去。
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int n;int l;const int MAXN=1000005;char s[MAXN];int nxt[MAXN];int num[MAXN];int sum[MAXN];const int mod=1e9+7;void solve(){nxt[1]=0;int L=strlen(s+1);int j=0;for(int i=2;i<=L;i++){while(j&&s[j+1]!=s[i]) j=nxt[j];if(s[j+1]==s[i]) j++;nxt[i]=j;}memset(sum,0,sizeof sum);memset(num,0,sizeof num);sum[0]=1;for(int i=1;i<=L;i++) sum[i]=sum[nxt[i]]+1;j=0;for(int i=2;i<=L;i++){while(j&&s[j+1]!=s[i]) j=nxt[j];if(s[j+1]==s[i]) j++;while(j>i/2) j=nxt[j];//防止超过i/2导致重叠num[i]=sum[j]-1;}// for(int i=1;i<=L;i++) printf("%d ",num[i]);// puts("");long long ans=1;for(int i=1;i<=L;i++) ans=ans*(num[i]+1)%mod;printf("%lld\n",ans);}int main(){scanf("%d",&n);for(int i=0;i<n;i++){scanf("%s",s+1);solve();}return 0;}
见题解
hash:O(nlogn)
abba aba
偶数中心在空隙,奇数中心为某一字符
可以加一个符号将偶数变成奇数,如
#a#b#b#a#,回文串半径成为两倍
预处理一个数组表示能扩展的半径
abcbaabcba
1131111311
//暴力for(int i=1;i<=L;++i){r[i]=1;while(s[i-r[i]]==s[i+r[i]]) r[i]++}在边上加一个边界可以判断结束,最坏O(n^2)
如果我们要拓展i,我们找到i关于id的对称点(2id-i),该点的回文半径是已知的。
例如,
abacabac
12345678
求6处的b时,id=3,所以对称为2处的b。因此r[i]初值为r[2id-i],但观察发现其实还是能扩展的,所以还需要继续暴力。
int mx=max{i+r[i]-1}//能够达到最靠右的元素int id;//mx取到最大值时取到的ifor(int i=1;i<=L;++i){r[i]=min(r[2id-i],mx-i);//有可能超过半径while(s[i-r[i]]==s[i+r[i]]) r[i]++;if(i+r[i]-1>mx) mx=i+r[i]-1,id=i;}复杂度O(n).
第一段正hash=第二段反hash=第三段正hash=第四段反hash
也可以在manachar r[i]++处加一个check函数判断是否是双回文
由于回文串删掉两边仍是回文串,所以其实可以建一棵树。
查询字符串出现次数,然后可以打一个+1标记
打完以后再从叶到根push一遍,得到了所有回文串对应的出现次数
以hash来建树。
a串的后缀与b串LCP
先求b串后缀与自己的LCP
//暴力for(int i=1;i<=n;++i){p[i]=0;while(b[i+p[i]]==b[p[i]+1]) ++p[i];}
。。。没听懂,我腿裙吧.jpg
for(int i=1;i<=n;++i){p[i]=min(mx-i,p[i-id]);while(b[i+p[i]]==b[p[i]+1]) ++p[i];if(i+p[i]>mx){id=i;mx=i+p[i];}}
给你两个串A,B,可以得到从A的任意位开始的子串和B匹配的长度。
给定K个询问,对于每个询问给定一个x,求出匹配长度恰为x的位置有多少个。
模板题
#include<bits/stdc++.h>using namespace std;int n,m,k;char sa[210005],sb[210005];int p[200005],q[200005];int ans[200005];int main(){scanf("%d%d%d%s%s",&n,&m,&k,sa,sb);//calc sbint mx=0,id=0;p[0]=m;for(int i=1;i<m;i++){if(i<=mx){p[i]=min(mx-i,p[i-id]);}while(sb[i+p[i]]&&sb[i+p[i]]==sb[p[i]]) p[i]++;if(i+p[i]>mx){mx=i+p[i];id=i;}}mx=0,id=0;for(int i=0;i<n;i++){if(i<=mx){q[i]=min(mx-i,p[i-id]);}while(sa[i+q[i]]&&sb[q[i]]&&sa[i+q[i]]==sb[q[i]]) q[i]++;if(i+q[i]>mx){mx=i+q[i];id=i;}}for(int i=0;i<n;i++) ans[q[i]]++;for(int i=0,x;i<k;i++){scanf("%d",&x);printf("%d\n",ans[x]);}return 0;}
每个节点代表一个串
int trie[100000][26];int tot;void insert(char* s){int p=0;for(int i=0;s[i];++i){if(trie[p][s[i]-'a') id=trie[p][s[i]-'a'];else id=trie[p][s[i]-'a']=++tot;}flag[p]++;//记录以p结尾的有几个}
直接插入,DFS,遇见flag>0的就输出来
两个点的LCA的深度是LCP
已知数组a,求max{a[i]^a[j]}
对每个a[i],找一个抑或最大的a[j]
由于1^1=0,1^0=1,0^0=0
将每个数变成二进制串,将所有的串建成Trie树,从根开始走,然后贪心,优先为1。
把Trie树上不存在的孩子用它fail节点的孩子补出来。
fail节点的概念与KMP的next类似,可以用一次bfs来求出。
若trie[i][x]存在,则fail[trie[i][x]]=trie[fail[i]][x]。
否则,trie[i][x]=trie[fail[i]][x]。
这啥玩意啊.jpg
多模式串匹配模板题。
#include<cstdio>#include<cstring>#include<algorithm>#include<queue>//by zrt//problem:using namespace std;int tt;int n;int pos;int nxt[500005][26];int fail[500005];int flag[500005],ok[500005];int newnode(){memset(nxt[pos],0,sizeof nxt[pos]);fail[pos]=flag[pos]=ok[pos]=0;return pos++;}char s[1000005];void insert(char*s){int p=0;for(int i=0;s[i];i++){p=nxt[p][s[i]-'a']?nxt[p][s[i]-'a']:nxt[p][s[i]-'a']=newnode();}flag[p]++;}queue<int> q;void makefail(){q.push(0);while(!q.empty()){int x=q.front();q.pop();for(int i=0;i<26;i++){if(nxt[x][i]){q.push(nxt[x][i]);if(x)fail[nxt[x][i]]=nxt[fail[x]][i];}else nxt[x][i]=nxt[fail[x]][i];}}}bool vis[500005];int find(char*s){memset(vis,0,sizeof vis);int p=0;int ans=0;for(int i=0;s[i];i++){p=nxt[p][s[i]-'a'];for(int j=p;j;j=fail[j]){if(vis[j]) break;vis[j]=1;if(flag[j]) ok[j]=1;}}for(int i=1;i<pos;i++) if(ok[i]) ans+=flag[i];return ans;}int main(){#ifdef LOCALfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);#endifscanf("%d",&tt);while(tt--){pos=1;memset(nxt[0],0,sizeof nxt[0]);scanf("%d",&n);for(int i=0;i<n;i++){scanf("%s",s);insert(s);}makefail();scanf("%s",s);printf("%d\n",find(s));}return 0;}
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
读入病毒代码;
判断是否存在一个无限长的安全代码;
将结果输出
如果Trie图中存在环就能生成无限长代码。否则就挂了。
找环拓扑即可。