poj3167(kmp)

题目链接: http://poj.org/problem?id=3167

 

题意: 给出两串数字 s1, s2, 求主串 s1 中的 s2 匹配数并输出每个匹配的开头位置. 区间 [l, r] 是 s2 的一个匹配当且仅当 s1[i] 是 [l, r] 中的第 s2[i - l] 大元素, 其中 l <= i <= r.

 

思路: 首先有个结论, 两个串的排名串相等当且仅当这两个串的每个位置上的元素前面等于它的元素个数和小于它的元素个数都相等. 那么可以先花 s * n + s * m 的时间预处理一下 vis1[i][j] 为 s1 前 i 个数字中有多少个小于等于 j, vis2[i][j] 为 s2 前 i 个数字中有多少个小于等于j. 然后可以用 kmp 匹配即可. 注意一下和一般 kmp 不同的是, 在一般 kmp 中的 s2[i] == s2[j] 和 s1[i] == s2[j] 条件要换成区间 (i - j, i] 中小于等于 s2[i] 的数和区间 [1, j] 中小于等于 s2[j] 的数都相等以及区间 (i - j, i] 中小于等于 s1[i] 的数和区间 [1, j] 中小于等于 s2[j] 的数都相等. 这是由前面那个结论决定的.

 

代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 using namespace std;
 5 
 6 const int MAXN = 1e5 + 10;
 7 int n, m, s;
 8 int s1[MAXN], s2[MAXN], nxt[MAXN];
 9 int vis1[MAXN][30], vis2[MAXN][30], sol[MAXN], tot;
10 //vis1[i][j]为s1前i个数字中有多少个小于等于j
11 //vis2[i][j]为s2前i个数字中有多少个小于等于j
12 
13 void init(void){
14     memset(nxt, 0, sizeof(nxt));
15     memset(vis1, 0, sizeof(vis1));
16     memset(vis2, 0, sizeof(vis2));
17     tot = 0;
18 }
19 
20 void gel(void){
21     for(int i = 1; i <= n; i++){
22         for(int j = 1; j <= s; j++){
23             vis1[i][j] = vis1[i - 1][j];
24         }
25         vis1[i][s1[i]]++;
26     }
27     for(int i = 1; i <= m; i++){
28         for(int j = 1; j <= s; j++){
29             vis2[i][j] = vis2[i - 1][j];
30         }
31         vis2[i][s2[i]]++;
32     }
33 }
34 
35 void get_nxt(void){
36     int i = 1, j = 0;
37     while(i <= m){
38         int cnt1 = 0, cnt2 = 0, cc1 = 0, cc2 = 0;
39         for(int k = 1; k < s2[i]; k++){
40             cnt1 += vis2[i][k] - vis2[i - j][k]; //累计s2区间(i-j,i]中小于s2[i]的数
41         }
42         cc1 = vis2[i][s2[i]] - vis2[i - j][s2[i]]; //s2区间(i-j,i]中等于s2[i]的数
43         for(int k = 1; k < s2[j]; k++){
44             cnt2 += vis2[j][k]; //累计s2区间[1,j]中小于s2[j]的数
45         }
46         cc2 = vis2[j][s2[j]]; //s2区间[1,j]中等于s2[j]的数
47         if(j == 0 || (cnt1 == cnt2 && cc1 == cc2)) nxt[++i] = ++j;
48         else j = nxt[j];
49     }
50 }
51 
52 
53 void kmp(void){
54     int i = 1, j = 1;
55     while(i <= n){
56         int cnt1 = 0, cnt2 = 0, cc1 = 0, cc2 = 0;
57         for(int k = 1; k < s1[i]; k++){ //累计s1区间(i-j,i]中小于s1[i]的数
58             cnt1 += vis1[i][k] - vis1[i - j][k];
59         }
60         cc1 = vis1[i][s1[i]] - vis1[i - j][s1[i]]; //s1区间(i-j,i]中等于s1[i]的数
61         for(int k = 1; k < s2[j]; k++){
62             cnt2 += vis2[j][k];//累计s2区间[1,j]中小于s2[j]的数
63         }
64         cc2 = vis2[j][s2[j]];//s2区间[1,j]中等于s2[j]的数
65         if(j == 0 || (cnt1 == cnt2 && cc1 == cc2)){
66             i++;
67             j++;
68         }else j = nxt[j];
69         if(j == m + 1){
70             sol[tot++] = i - j + 1;
71             j = nxt[j];
72         }
73     }
74 }
75 
76 int  main(void){
77     while(~scanf("%d%d%d", &n, &m, &s)){
78         for(int i = 1; i <= n; i++){
79             scanf("%d", &s1[i]);
80         }
81         for(int i = 1; i <= m; i++){
82             scanf("%d", &s2[i]);
83         }
84         init();
85         gel();
86         get_nxt();
87         kmp();
88         printf("%d\n", tot);
89         for(int i = 0; i < tot; i++){
90             printf("%d\n", sol[i]);
91         }
92     }
93     return 0;
94 }
View Code

 

posted @ 2017-08-11 15:26  geloutingyu  阅读(313)  评论(0编辑  收藏  举报