字符串hash

hash函数:BKERHash、APHash、DJBHash、JSHash等,一般用BKERHash

例题  hdu 2648

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=10005;
const int INF=0x3fffffff;
typedef long long LL;
//字符串hash的应用
int n,m;
struct node{
	char name[40];
	int price;
};
vector<node> q[maxn];  //用于解决冲突
unsigned int BKDRHash(char *str){  //hash函数 
	unsigned int seed=31,key=0;
	while(*str){
		key=key*seed+(*str++);
	}
	return key&0x7fffffff;
} 

int main(){
	int p[maxn];
	char s[35];
	node t;
	int mom,key,x,rank,len;
	while(cin>>n){
		for(int i=0;i<maxn;i++) q[i].clear();
		for(int i=0;i<n;i++){
			cin>>t.name;
			key=BKDRHash(t.name)%maxn; //记得取余 
			q[key].push_back(t);
		}
		cin>>m;
		
		while(m--){
			rank=0;len=0;
			for(int i=0;i<n;i++){
				cin>>x>>s;
				key=BKDRHash(s)%maxn;
				for(int j=0;j<q[key].size();j++){
					if(strcmp(q[key][j].name,s)==0){
						q[key][j].price+=x;
						if(strcmp(s,"memory")==0)  mom=q[key][j].price;
						else p[len++]=q[key][j].price;
						break;
					}
				}
			}
			for(int i=0;i<len;i++) if(p[i]>mom) rank++;
			cout<<rank+1<<endl;
		}
	}
return 0;
}

P3370 【模板】字符串哈希

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
typedef unsigned long long ull;
char s[N];
ull h[N],ans[N];
const int pp=131;
int n;
ull calc(char *s,int n){
	h[0]=0;
	for(int i=1;i<=n;i++) h[i]=h[i-1]*pp+s[i];
	return h[n];
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		int m=strlen(s+1);
		ans[i]=calc(s,m);
	}
	sort(ans+1,ans+1+n);
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(ans[i]!=ans[i-1]) cnt++;
	}
	printf("%d",cnt);
	return 0;
}

 

最小表示法

  P1368 【模板】最小表示法

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 7e5;
typedef long long LL;
int n;
int s[N];
//最小表示法
int get_min(){
	for(int i=1;i<=n;i++) s[n+i]=s[i]; //循环就放两倍长
	int i=1,j=2,k=0;
	while(i<=n&&j<=n){
		for(k=0;k<n&&s[i+k]==s[j+k];k++);
		s[i+k]>s[j+k] ? i=i+k+1:j=j+k+1;
		if(i==j) j++;
	} 
	return min(i,j);
} 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&s[i]);
	int k=get_min();
	for(int i=0;i<n;i++){  //注意下标 
		printf("%d ",s[k+i]);
	} 
	return 0;
}

  

 

字典树Trie树

时间复杂度:查找和插入单词的复杂度都是O(M),M是待插入/待查找单词的长度

空间复杂度:公共前缀只存一次

应用:

(1)字符串检索

(2)词频统计

(3)字符串排序:插入的时候,在树的平级按顺序插入,建好之后先序遍历即可

(4)前缀匹配

例如: hdu 1251 统计难题 求以某个字符串 为前缀的单词数量

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
///字典树
//求以某个字符串 为前缀的单词数量
int trie[1000010][26];  //用数组定义字典树,!!!存储下一个字符的位置
int num[1000010]={0};  //单词数量
int pos=1; //当前新分配的存储位置,其实就是所有出现的
void inser(char str[]){
	int p=0;
	for(int i=0;str[i];i++){
		int n=str[i]-'a';
		if(trie[p][n]==0)
			trie[p][n]=pos++;  //不存在就分配一个 
		p=trie[p][n]; //向下延旭
		num[p]++; //以此为前缀的单词数量++ 
	}
} 
int fin(char str[]){
	int p=0;
	for(int i=0;str[i];i++){  //如果存在这个公共前缀,那么肯定能够遍历完 
		int n=str[i]-'a';
		if(trie[p][n]==0) return 0; //不存在
		p=trie[p][n]; 
		//cout<<p<<endl;
	}
	return num[p];
}
int main(){
	char aa[11];
	while(gets(aa)){
		if(!strlen(aa)) break;
		inser(aa);
		
	}
	while(gets(aa)){
		cout<<fin(aa)<<endl;
	}
return 0;
}

KMP算法

单模匹配算法,复杂度为O(n+m),主要就是对模式串求出next数组,然后与文本串匹配,在匹配时,文本串不会回退,next就是模式串匹配失败是回退的地方,所以复杂度低

 例题: 剪花布条(要有个数)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//KMP模板题
char str[maxn],pat[maxn];
int cnt;
int Next[maxn];
void getnext(char *st,int len){
	Next[0]=0;
	Next[1]=0;  //初始化
	for(int i=1;i<len;i++){
		int j=Next[i];
		while(j&&st[i]!=st[j]) j=Next[j];
		Next[i+1]=(st[i]==st[j])?j+1:0;
	}
}
void kmp(char *s,char *p){
	int last=-1;  //为什么要这个,看例子就知道了,这个是最后一次得到一个完整的模式串的位置
	int n=strlen(s);
	int m=strlen(p);
	getnext(p,m);
	int j=0;
	for(int i=0;i<n;i++){
		while(j&&s[i]!=p[j]) j=Next[j];
		if(s[i]==p[j]) j++;
		if(j==m){
			if(i-last>=m){
				cnt++;
				last=i;
			}
		}
	} 
}
int main(){
	while(~scanf("%s",str)){
		if(str[0]=='#') break;
		scanf("%s",pat);
		cnt=0;
		kmp(str,pat);
		printf("%d\n",cnt);
	}
return 0;
}

模板

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
using namespace std;
//KMP算法:匹配字符串,O(n+m)
//next数组:含义是什么?next[i]就是所求最长相等前后缀的前缀最后一位的下标,用递推的方法来求
const int maxn=1001;
int next[maxn];
string text,pattern; //文本串、模式串
void getnext(string s,int len) {  //针对模式串的 
	int j=-1;
	next[0]=-1; //第一步:初始化
	for(int i=1; i<len; i++) {
		//一开始有一个关于模式串的大循环
		while(j!=-1&&s[i]!=s[j+1]) j=next[j]; //j不断回退(j=-1或s[i]==s[j+1]
		if(s[i]==s[j+1]) j++; //相等:长度加1
		next[i]=j;
	}
}
//KMP算法:令i指向text当前需要匹配的一位,令j指向pattern当前已经匹配了的一位(text[i]==pattern[j+1])
//next[j]数组的意思是:如果第j+1为匹配失败是,应该回退的位置
//判断pattern是不是text的子串
bool KMP(string a,string b) {
	int n=a.length(),m=b.length();
	getnext(b,m); //计算匹配串的next数组
	int j=-1; //初始化,表示当前没有任意一位被匹配
	for(int i=0; i<n; i++) {	//匹配文本串的每一位
		while(j!=-1&&a[i]!=b[j+1]) j=next[j];
		if(a[i]==b[j+1]) j++;
		if(j==m-1) return 1; //如果匹配到了模式串的最后一位:匹配成功
		//就没有了
	}
	return 0;
}
//计算模式串出现的次数
int KMP2(string a,string b) {
	int n=a.length(),m=b.length();
	getnext(b,m); //计算匹配串的next数组
	int ans=0;
	int j=-1; //初始化,表示当前没有任意一位被匹配
	for(int i=0; i<n; i++) {	//匹配文本串的每一位
		while(j!=-1&&a[i]!=b[j+1]) j=next[j];
		if(a[i]==b[j+1]) j++;
		if(j==m-1) {
			ans++;    //次数加1;
			j=next[j];  //让j回退到next[j]继续匹配
		}
		//就没有了
	}
	cout<<"the text has "<<ans<<" pattern "<<endl;
}
//KPM优化:将next数组换为 nextval[]数组,为了避免不必要的回退!! 
//nextval数组的含义是第j+1位匹配失败了,第j位应该退回的最佳位置 
//失去了next数组本身的含义,但是降低了时间复杂度 
int nextval[maxn];
void getnextval(string a,int len){
	int j=-1;
	nextval[0]=-1; //初始化
	for(int i=1;i<len;i++){
		while(j!=-1&&a[i]!=a[j+1]) j=nextval[j]; 
	if(a[i]==a[j+1]) j++;
	//next数组的情况:直接:next[i]=j; 
	if(j==-1||a[i+1]!=a[j+1]) nextval[i]=j; //这是不需要回退的情况
	else nextval[i]=nextval[j]; 
}
	//i+1不需要判断是否越界:不必要(思考p463) 
}
int main() {
	cin>>text>>pattern;
	if(KMP(text,pattern)==1) cout<<"YES"<<endl; 
	else cout<<"NO"<<endl;
	
	return 0;
}

  

AC自动机

多模匹配算法,在一个文本串中匹配查找多个子串,把所有的模式串P弄成字典树,复杂度为O(km+nm)

这个算法的理解是:回退边和转移边

视频讲的也很好:187 AC自动机 - 董晓 - 博客园 (cnblogs.com)

#include <queue>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn =  2*1e6+9;

int trie[maxn][26]; //字典树
int cntword[maxn];  //记录该单词出现次数
int fail[maxn];     //失败时的回溯指针
int cnt = 0;

void insertWords(string s){
    int root = 0;
    for(int i=0;i<s.size();i++){
        int next = s[i] - 'a';
        if(!trie[root][next])
            trie[root][next] = ++cnt;
        root = trie[root][next];
    }
    cntword[root]++;      //当前节点单词数+1
}
void getFail(){
    queue <int>q;
    for(int i=0;i<26;i++){      //将第二层所有出现了的字母扔进队列
        if(trie[0][i]){
            fail[trie[0][i]] = 0;
            q.push(trie[0][i]);
        }
    }

//fail[now]    ->当前节点now的失败指针指向的地方
////tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
    while(!q.empty()){
        int now = q.front();
        q.pop();

        for(int i=0;i<26;i++){      //查询26个字母
            if(trie[now][i]){
                //如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
                //有点绕,为了方便理解特意加了括号

                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            }
            else//否则就让当前节点的这个子节点
                //指向当前节点fail指针的这个子节点
                trie[now][i] = trie[fail[now]][i];
        }
    }
}


int query(string s){
    int now = 0,ans = 0;
    for(int i=0;i<s.size();i++){    //遍历文本串
        now = trie[now][s[i]-'a'];  //从s[i]点开始寻找
        for(int j=now;j && cntword[j]!=-1;j=fail[j]){
            //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
            ans += cntword[j];
            cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
        }
    }
    return ans;
}

int main() {
    int n;
    string s;
    cin >> n;
    for(int i=0;i<n;i++){
        cin >> s ;
        insertWords(s);
    }
    fail[0] = 0;
    getFail();
    cin >> s ;
    cout << query(s) << endl;
    return 0;
}

  

 

 posted on 2020-04-15 13:14  shirlybabyyy  阅读(270)  评论(0编辑  收藏  举报