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 ",ans); 112 for (int i = 1 ; i <= ans ; i++) printf("%d ",id[i]); 113 return 0; 114 }
这道题还有一个解法:kmp + 树状数组,对于每个对应的位置,只需要排名相同就可以匹配,直接用树状数组维护有多少个比当前小,有多少个相等,就可以知道当前位置是否相等。显然每个位置都相等,可以推得所有都相等。注意跳fail时两个串要分别进行对应的修改
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define maxn 1000020 5 #define lowbit(x) (x&(-x)) 6 7 int s1[maxn],s2[maxn]; 8 int n,m,a[maxn],b[maxn],S,id[maxn],ans,fail[maxn]; 9 10 inline int query(int s[],int d){ 11 int ans = 0; 12 for (int i = d ; i >= 1 ; i -= lowbit(i)) ans += s[i]; 13 return ans; 14 } 15 inline void add(int s[],int d,int x){ 16 for (int i = d ; i <= S ; i += lowbit(i)) s[i] += x; 17 } 18 inline bool jud(int x,int y){ 19 if ( query(s1,x - 1) == query(s2,y - 1) && query(s1,x) == query(s2,y) ) return 1; 20 return 0; 21 } 22 void getfail(){ 23 int p = 1,q = 0; 24 while ( p < m ){ 25 if ( !jud(b[p + 1],b[q + 1]) ){ 26 int now = q; 27 q = fail[q]; 28 for (int i = now ; i > q ; i--){ 29 add(s2,b[i],-1); 30 } 31 for (int i = p - now + 1 ; i < p - q + 1 ; i++){ 32 add(s1,b[i],-1); 33 } 34 } 35 else fail[++p] = ++q , add(s1,b[p],1) , add(s2,b[q],1); 36 if ( !q && !jud(b[p + 1],b[q + 1]) ) p++; 37 } 38 // for (int i = 1 ; i <= m ; i++) cout<<fail[i]<<" "; 39 // cout<<endl; 40 } 41 void kmp(){ 42 for (int i = 0 ; i <= S ; i++) s1[i] = s2[i] = 0; 43 int p = 0,q = 0; 44 while ( p < n ){ 45 if ( !jud(a[p + 1],b[q + 1]) || q == m ){ 46 int now = q; 47 q = fail[q]; 48 for (int i = now ; i > q ; i--){ 49 add(s2,b[i],-1); 50 } 51 for (int i = p - now + 1 ; i < p - q + 1 ; i++){ 52 add(s1,a[i],-1); 53 } 54 } 55 else p++, q++, add(s1,a[p],1), add(s2,b[q],1); 56 if ( q == m ) id[++ans] = p - m + 1; 57 if ( !q && !jud(a[p + 1],b[q + 1]) ) p++; 58 } 59 } 60 int main(){ 61 freopen("input.txt","r",stdin); 62 scanf("%d %d %d",&n,&m,&S); 63 for (int i = 1 ; i <= n ; i++) scanf("%d",&a[i]); 64 for (int i = 1 ; i <= m ; i++) scanf("%d",&b[i]); 65 //b[m + 1] = '#'; 66 getfail(); 67 kmp(); 68 printf("%d ",ans); 69 for (int i = 1 ; i <= ans ; i++) printf("%d ",id[i]); 70 return 0; 71 }
总结:字符串hash是用来比较字符串的重要方法,可见hash还可以用来比较某些特定意义下的相等:如排名相同即相等。有两种hash方法,一种是一位置为关键字,一种是以排名为关键字。第一种通常实用,但第二种可以在位置比较确定的时候用来比较两个字符串的相对顺序关系。还是要根据题目来定