Hash比较字符串小结

做了两道hash比较字符串的题。分别使用同一种思想不同的方法比较字符串。

1.

给你两个1-n、1-m的排列A,B,问A中的字符串有多少子序列与B串等价(同时减d后与B串相同)

题解:考虑A的子序列与B串等价只需要A中的每个数的排名与B相同且值域1+d-m+d。这样我们只需要维护A串的hash值,以位置为值域,hash=sigma(dt[i] *base^pos);因为是子序列,所以位置比较难维护,则用线段树维护位置,易于合并。

 1 #include<iostream>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cstdio>
 5 using namespace std;
 6 #define maxn 200020
 7 #define mod 1000000007
 8 
 9 typedef long long LL;
10 struct node{
11     int ls,rs,d,t,dt;
12     LL hash;
13 }sgt[maxn * 2];
14 int tot,n,m,a[maxn],b[maxn],id[maxn],root,ans;
15 LL hash_a,pow[maxn],Fpow[maxn];
16 int p = 17;
17 
18 void build(int &now,int l,int r){
19     now = ++tot;
20     if ( l == r ) return;
21     int mid = (l + r) >> 1;
22     build(sgt[now].ls,l,mid);
23     build(sgt[now].rs,mid + 1,r);
24 }
25 inline void update(int now){
26     sgt[now].t = sgt[sgt[now].ls].t + sgt[sgt[now].rs].t;
27     sgt[now].hash = (sgt[sgt[now].rs].hash + (sgt[sgt[now].ls].hash * pow[sgt[sgt[now].rs].t]) % mod) % mod;
28 }
29 inline void add(int now,int d){
30     sgt[now].d += d;
31     sgt[now].hash = ((sgt[now].hash + (LL) d * Fpow[sgt[now].t - 1]) % mod + mod) % mod;
32 }
33 inline void pushdown(int now){
34     if ( sgt[now].d ){
35         if ( sgt[now].ls ) add(sgt[now].ls,sgt[now].d);
36         if ( sgt[now].rs ) add(sgt[now].rs,sgt[now].d);
37         sgt[now].d = 0;
38     }
39 }
40 void modify(int now,int l,int r,int pos,int k){
41     if ( l == r ){
42         if ( !k ) sgt[now].t = 0 , sgt[now].hash = 0;
43         else sgt[now].t = 1 , sgt[now].hash = k;
44         return;
45     }
46     pushdown(now);
47     int mid = (l + r) >> 1;
48     if ( pos <= mid ) modify(sgt[now].ls,l,mid,pos,k);
49     else modify(sgt[now].rs,mid + 1,r,pos,k);
50     update(now);
51 }
52 void modify(int now,int l,int r,int ls,int rs,int k){
53     if ( ls <= l && rs >= r ){
54         add(now,k);
55         return;
56     }
57     pushdown(now);
58     int mid = (l + r) >> 1;
59     if ( ls <= mid ) modify(sgt[now].ls,l,mid,ls,rs,k);
60     if ( rs > mid ) modify(sgt[now].rs,mid + 1,r,ls,rs,k);
61     update(now);
62 }
63 void init(){
64     pow[0] = Fpow[0] = 1;
65     for (int i = 1 ; i <= n ; i++){
66         pow[i] = (pow[i - 1] * (LL) p) % mod , Fpow[i] = (Fpow[i - 1] + pow[i]) % mod;
67     }
68     for (int i = 1 ; i <= n ; i++){
69         hash_a = (hash_a * (LL) p + (LL) a[i]) % mod;
70     }
71     for (int i = 1 ; i <= m ; i++) id[b[i]] = i;
72     build(root,1,m);
73     for (int i = 1 ; i <= n ; i++){
74         modify(root,1,m,id[i],i);
75     }
76 }
77 int main(){
78     scanf("%d %d",&n,&m);
79     for (int i = 1 ; i <= n ; i++) scanf("%d",&a[i]);
80     for (int i = 1 ; i <= m ; i++) scanf("%d",&b[i]);
81     init();
82     for (int i = 1 ; i <= m - n ; i++){
83         if ( hash_a == sgt[root].hash ) ans++;
84         modify(root,1,m,id[i],0);
85         modify(root,1,m,1,m,-1);
86         modify(root,1,m,id[n + i],n);
87     }
88     if ( hash_a == sgt[root].hash ) ans++;
89     printf("%d\n",ans);
90     return 0;
91 }
View Code

2.

Bzoj 1461 字符串的匹配
给两个长度为n、m的序列A、B,问A中有多少个子串与B等价(相同位置的值排名相同)

题解:同样考虑hash。因为A是子序列,值的排名难以修改,多以用把排名用线段树的位置维护。Hash=sigma(id *base^sort[i]);同样可以比较两串是否相等。注意如果有很多相同值的细节问题:要把他们按位置先后排名

不过这样因为取模常数过大,bz上很难过

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 using namespace std;
  6 #define maxn 500020
  7 #define maxm 10020
  8 #define mod 1000000007
  9 #define __O3 __attribute__((optimize("O3")))
 10 typedef long long LL;
 11 struct node{
 12     int add,ls,rs,sz;
 13     LL hash;
 14 }sgt[maxm * 4];
 15 int tot,n,m,s,root;
 16 int a[maxn],b[maxn],num[maxn],ans,id[maxn],vis[maxn];
 17 LL pow[maxn],Fpow[maxn],hash_b,inv;
 18 const LL p = 31;
 19 
 20 __O3 void pre(){
 21     //memcpy(num,b,sizeof(b));
 22     for (int i = 1 ; i <= m ; i++) num[i] = b[i];
 23     sort(b + 1,b + m + 1);
 24     for (int i = 1 ; i <= m ; i++){
 25         int now = num[i];   
 26         num[i] = lower_bound(b + 1,b + m + 1,num[i]) - b;
 27         num[i] += vis[now];
 28         vis[now]++;
 29     }
 30 }
 31 __O3 void build(int &now,int l,int r){
 32     now = ++tot;
 33     if ( l == r ) return;
 34     int mid = (l + r) >> 1;
 35     build(sgt[now].ls,l,mid);
 36     build(sgt[now].rs,mid + 1,r);
 37 }
 38 __O3 inline void add(int now,int d){
 39     sgt[now].add += d; 
 40     sgt[now].hash = ((sgt[now].hash + (LL) d * (Fpow[sgt[now].sz] - 1)) % mod + mod) % mod;
 41 }
 42 __O3 inline void pushdown(int now){
 43     if ( sgt[now].add != 0 ){
 44         if ( sgt[now].ls ) add(sgt[now].ls,sgt[now].add);
 45         if ( sgt[now].rs ) add(sgt[now].rs,sgt[now].add);
 46         sgt[now].add = 0;
 47     }
 48 }
 49 __O3 inline void update(int now){
 50     sgt[now].sz = sgt[sgt[now].ls].sz + sgt[sgt[now].rs].sz;
 51     sgt[now].hash = (sgt[sgt[now].ls].hash + sgt[sgt[now].rs].hash * pow[sgt[sgt[now].ls].sz]) % mod;
 52 }
 53 //在某个值域上加一个新的位置,或删除一个位置
 54 __O3 void modify(int now,int l,int r,int pos,int d){ //线段树:以值域为位置,每个值域记录在是他的id这和。值域表示的是排名
 55     if ( l == r ){
 56         if ( d < 0 ) sgt[now].sz-- , sgt[now].hash = (((sgt[now].hash + (LL)d * pow[1]) % mod + mod) % mod * inv) % mod;
 57         if ( d > 0 ) sgt[now].sz++ , sgt[now].hash = (sgt[now].hash + (LL)d * pow[sgt[now].sz]) % mod;    
 58         return;
 59     }
 60     pushdown(now);
 61     int mid = (l + r) >> 1;
 62     if ( pos <= mid ) modify(sgt[now].ls,l,mid,pos,d);
 63     else modify(sgt[now].rs,mid + 1,r,pos,d);
 64     update(now);
 65 }
 66 __O3 void modify(int now,int l,int r,int ls,int rs,int d){ //修改位置,将整体左移
 67     if ( ls <= l && rs >= r ){
 68         add(now,d);
 69         return;
 70     }
 71     pushdown(now);
 72     int mid = (l + r) >> 1;
 73     if ( ls <= mid ) modify(sgt[now].ls,l,mid,ls,rs,d);
 74     if ( rs > mid ) modify(sgt[now].rs,mid + 1,r,ls,rs,d);
 75     update(now);
 76 }
 77 __O3 inline LL power(LL x,int y){
 78     LL res = 1;
 79     while ( y ){
 80         if ( y & 1 ) res = (res * x) % mod;
 81         x = (x * x) % mod;
 82         y >>= 1;
 83     }
 84     return res % mod;
 85 }
 86 __O3 void init(){
 87     pre();
 88     pow[0] = Fpow[0] = 1;
 89     for (int i = 1 ; i <= m ; i++) pow[i] = (pow[i - 1] * p) % mod , Fpow[i] = (Fpow[i - 1] + pow[i]) % mod;
 90     inv = power(p,mod - 2);
 91     for (int i = 1 ; i <= m ; i++) hash_b = (hash_b + pow[num[i]] * (LL) i) % mod;
 92     build(root,1,s);
 93     for (int i = 1 ; i <= m ; i++){
 94         modify(root,1,s,a[i],i);
 95     }
 96 }
 97 __O3 int main(){
 98     freopen("match.in","r",stdin);
 99     freopen("match.out","w",stdout);
100     scanf("%d %d %d",&n,&m,&s);
101     for (int i = 1 ; i <= n ; i++) scanf("%d",&a[i]);
102     for (int i = 1 ; i <= m ; i++) scanf("%d",&b[i]);
103     init();
104     for (int i = 2 ; i <= n - m + 1 ; i++){
105         if ( sgt[root].hash == hash_b ) id[++ans] = i - 1;;
106         modify(root,1,s,a[i - 1],-1);
107         modify(root,1,s,1,s,-1);
108         modify(root,1,s,a[i + m - 1],m);
109     }
110     if ( sgt[root].hash == hash_b ) id[++ans] = n - m + 1;
111     printf("%d\n",ans);
112     for (int i = 1 ; i <= ans ; i++) printf("%d\n",id[i]);
113     return 0;
114 }
View Code

这道题还有一个解法:kmp + 树状数组,对于每个对应的位置,只需要排名相同就可以匹配,直接用树状数组维护有多少个比当前小,有多少个相等,就可以知道当前位置是否相等。显然每个位置都相等,可以推得所有都相等。注意跳fail时两个串要分别进行对应的修改

#include<iostream>
#include<cstdio>
using namespace std;
#define maxn 1000020
#define lowbit(x) (x&(-x))

int s1[maxn],s2[maxn];
int n,m,a[maxn],b[maxn],S,id[maxn],ans,fail[maxn];

inline int query(int s[],int d){
    int ans = 0;
    for (int i = d ; i >= 1 ; i -= lowbit(i)) ans += s[i];
    return ans;
}
inline void add(int s[],int d,int x){
    for (int i = d ; i <= S ; i += lowbit(i)) s[i] += x;
}
inline bool jud(int x,int y){
    if ( query(s1,x - 1) == query(s2,y - 1) && query(s1,x) == query(s2,y) ) return 1;
    return 0;
}
void getfail(){
    int p = 1,q = 0;
    while ( p < m ){
        if ( !jud(b[p + 1],b[q + 1]) ){
            int now = q;
               q = fail[q];
            for (int i = now ; i > q ; i--){
                add(s2,b[i],-1);    
            }
            for (int i = p - now + 1 ; i < p - q + 1 ; i++){
                add(s1,b[i],-1);
            }        
        }
        else fail[++p] = ++q , add(s1,b[p],1) , add(s2,b[q],1);
        if ( !q && !jud(b[p + 1],b[q + 1]) ) p++;
    }
//    for (int i = 1 ; i <= m ; i++) cout<<fail[i]<<" ";
//    cout<<endl;
}
void kmp(){
    for (int i = 0 ; i <= S ; i++) s1[i] = s2[i] = 0;
    int p = 0,q = 0;
    while ( p < n ){
        if ( !jud(a[p + 1],b[q + 1]) || q == m ){
            int now = q;
               q = fail[q];
            for (int i = now ; i > q ; i--){
                add(s2,b[i],-1);
            }
            for (int i = p - now + 1 ; i < p - q + 1 ; i++){
                add(s1,a[i],-1);
            }
        }
        else p++, q++, add(s1,a[p],1), add(s2,b[q],1);
        if ( q == m ) id[++ans] = p - m + 1;
        if ( !q && !jud(a[p + 1],b[q + 1]) ) p++;
    }
}
int main(){
    freopen("input.txt","r",stdin);
    scanf("%d %d %d",&n,&m,&S);
    for (int i = 1 ; i <= n ; i++) scanf("%d",&a[i]);
    for (int i = 1 ; i <= m ; i++) scanf("%d",&b[i]);
    //b[m + 1] = '#';
    getfail();
    kmp();
    printf("%d\n",ans);
    for (int i = 1 ; i <= ans ; i++) printf("%d\n",id[i]);
    return 0;
}
View Code

总结:字符串hash是用来比较字符串的重要方法,可见hash还可以用来比较某些特定意义下的相等:如排名相同即相等。有两种hash方法,一种是一位置为关键字,一种是以排名为关键字。第一种通常实用,但第二种可以在位置比较确定的时候用来比较两个字符串的相对顺序关系。还是要根据题目来定

posted @ 2016-03-16 13:46  zhangqingqi  阅读(551)  评论(0编辑  收藏  举报