poj 3167 (kmp 好题)

来源:互联网 发布:it项目招标书 编辑:程序博客网 时间:2024/05/17 02:34
题目:http://poj.org/problem?id=3167
题目大意:给你两个数列a、b,长度分别为 n 、k,让你用 b 去匹配 a,只要是两者的相对大小对的上就算匹配,让你求出所有的
匹配的位置。
思路:KMP好题!kmp 是顺序匹配的,建设前面已经匹配了,现在正在匹配 i 和 j ,因为是相对大小,如果i位置的前面比a[ i ]的
数目和 j 位置比b[ j ] 小的数目相等而且i位置的前面与a[ i ]相等的数目和 j 位置比b[ j ] 相等的数目相等,那么就是可以匹配。
把 while 里面相等的那句话换一下,直接kmp就好了。基本的思想就是这样,实现上面那个的方法貌似有很多种,由于s比较小,就直接
暴力上了。其他的明天再看,这里有个博客,说的不错:http://www.cppblog.com/zxb/archive/2010/10/06/128782.aspx?opt=admin,还有用树状数组统计排名的,也可以用枚举法。。。 = = 

唉,这道题真是伤,一直WA,检查到半夜,终于发现,原来是把 while 写成 if 了,唉,无语啊。。。。 T^T

代码如下:

#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;int n,k,s;struct TT{    int len;    int num[111111];    int low[111111][33],equ[111111][33];} t,p;void init(TT &tmp){    for(int i =1;i<=tmp.len;i++)    {        for(int j = 1;j<=s;j++)        {            tmp.low[i][j] = tmp.low[i-1][j] + (tmp.num[i] < j ? 1 : 0);            tmp.equ[i][j] = tmp.equ[i-1][j] + (tmp.num[i] == j ? 1 : 0);        }    }}int fail[25555];int check(TT &a,TT &b,int i,int j){    if(a.low[i][a.num[i]] - a.low[i - j][a.num[i]] == b.low[j][b.num[j]]       && a.equ[i][a.num[i]] - a.equ[i - j][a.num[i]] == b.equ[j][b.num[j]])        return 1;    return 0;}void get_fail(){    fail[1] = 0;    int j = 0;    for(int i = 2;i<=k;i++)    {        if(j >= 1 && !check(p,p,i,j+1) )            j = fail[j];        if(check(p,p,i,j+1)) j++;        fail[i] = j;    }}vector <int> ans;void kmp(){    ans.clear();    get_fail();    int j = 0;    for(int i = 1;i<=n;i++)    {        while(j >= 1 && !check(t,p,i,j+1)) j = fail[j];        if(check(t,p,i,j+1)) j++;        if(j == k)        {            ans.push_back(i-k+1);            j = fail[j];        }    }}int main(){    while(~scanf("%d%d%d",&n,&k,&s))    {        for(int i = 1;i<=n;i++)            scanf("%d",&t.num[i]);        for(int i = 1;i<=k;i++)            scanf("%d",&p.num[i]);        t.len = n;        p.len = k;        init(t);        init(p);        kmp();        printf("%d\n",ans.size());        for(int i = 0;i<ans.size();i++)            printf("%d\n",ans[i]);    }    return 0;}

这个是树状数组版本,用树状数组看当前待匹配的值的排名,记当前匹配的是i和j+1,先把 i 这个值放进树状数组,看能不能匹配,如果
可以,那么这个值就插进去,不可以,记 len = j - fail[ j ],把 i 位置结尾的长度为 j+1 的字符串的前 len 个减掉。这里判断如果
j == k 时,也就是匹配找到一个答案时,也是要减掉这前面 len 个长度,不过前面开始是 i - j + 1,因为 j 之前 ++ 了,因为这个 WA 
了几次。。。 = =

树状数组版本代码如下:

#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;int n,k,s;struct TT{    int len;    int num[111111];    int low[111111],equ[111111];} t,p;int c[33];int lowbit(int x){    return x&(-x);}void update(int x,int val){    while(x <= s)    {        c[x] += val;        x += lowbit(x);    }}int sum(int x){    int cnt = 0;    while(x > 0)    {        cnt += c[x];        x -= lowbit(x);    }    return cnt;}void init(TT &tmp){    memset(c,0,sizeof(c));    for(int i =1;i<=tmp.len;i++)    {        update(tmp.num[i],1);        tmp.low[i] = sum(tmp.num[i]-1);        tmp.equ[i] = sum(tmp.num[i]) - sum(tmp.num[i]-1);        //printf("i = %d,low = %d,equ = %d\n",i,tmp.low[i],tmp.equ[i]);    }}int fail[25555];void get_fail(){    memset(c,0,sizeof(c));    fail[1] = 0;    int j = 0;    for(int i = 2;i<=k;i++)    {        update(p.num[i],1);        while(j >= 1 && !(sum(p.num[i]-1) == p.low[j+1] && sum(p.num[i]) - sum(p.num[i] - 1) == p.equ[j+1]))        {            for(int kk = i - j;kk < i - fail[j];kk++)                update(p.num[kk],-1);            j = fail[j];        }        if(sum(p.num[i]-1) == p.low[j+1] && sum(p.num[i]) - sum(p.num[i] - 1) == p.equ[j+1])        {            j++;        }        else update(p.num[i],-1);        fail[i] = j;        //printf("i = %d,fail = %d\n",i,fail[i]);    }}vector <int> ans;void kmp(){    ans.clear();    get_fail();    int j = 0;    memset(c,0,sizeof(c));    for(int i = 1;i<=n;i++)    {        update(t.num[i],1);        while(j >= 1 && !(sum(t.num[i]-1) == p.low[j+1] && sum(t.num[i]) - sum(t.num[i] - 1) == p.equ[j+1]))        {            for(int kk = i - j ;kk < i - fail[j];kk++)                update(t.num[kk],-1);            j = fail[j];        }        if(sum(t.num[i]-1) == p.low[j+1] && sum(t.num[i]) - sum(t.num[i] - 1) == p.equ[j+1])        {            j++;        }        else update(t.num[i],-1);        if(j == k)        {            ans.push_back(i-k+1);            for(int kk = i - j + 1;kk <= i - fail[j];kk++)                update(t.num[kk],-1);            j = fail[j];        }    }}int main(){    while(~scanf("%d%d%d",&n,&k,&s))    {        for(int i = 1;i<=n;i++)            scanf("%d",&t.num[i]);        for(int i = 1;i<=k;i++)            scanf("%d",&p.num[i]);        t.len = n;        p.len = k;        //init(t);        init(p);        kmp();        printf("%d\n",ans.size());        for(int i = 0;i<ans.size();i++)            printf("%d\n",ans[i]);    }    return 0;}


下面这个版本是根据上面那个博客来的,思路很好,代码如下:

#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;int n,k,s;struct TT{    int len;    int num[111111];    int low[111111],occ[33],hig[111111];} t,p;void init(TT &tmp){    memset(tmp.occ,-1,sizeof(tmp.occ));    tmp.occ[0] = -2;    tmp.occ[s+1] = -2;    for(int i =1;i<=tmp.len;i++)    {        if(tmp.occ[tmp.num[i]] == -1)            tmp.occ[tmp.num[i]] = i;        int j;        for(j = tmp.num[i] - 1;tmp.occ[j] == -1;j--);        tmp.low[i] = tmp.occ[j];        for(j = tmp.num[i] + 1;tmp.occ[j] == -1;j++);        tmp.hig[i] = tmp.occ[j];    }}int check(int* a,int* b,int pos){    if(a[p.occ[b[pos]]] != a[pos]) return 0;    if(p.low[pos] >= 0 && a[p.low[pos]] >= a[pos]) return 0;    if(p.hig[pos] >= 0 && a[p.hig[pos]] <= a[pos]) return 0;    return 1;}int fail[25555];void get_fail(){    fail[1] = 0;    int j = 0;    for(int i = 2;i<=k;i++)    {        while(j >= 1 && !check(p.num + (i - j - 1),p.num,j+1))        {            j = fail[j];        }        if(check(p.num + (i - j - 1),p.num,j+1))        {            j++;        }        fail[i] = j;        //printf("i = %d,fail = %d\n",i,fail[i]);    }}vector <int> ans;void kmp(){    ans.clear();    get_fail();    int j = 0;    for(int i = 1;i<=n;i++)    {        while(j >= 1 && !check(t.num + (i - j - 1),p.num,j+1))        {            j = fail[j];        }        if(check(t.num + (i - j - 1),p.num,j+1))        {            j++;        }        //printf("i = %d,j = %d\n",i,j);        if(j == k)        {            ans.push_back(i-k+1);            j = fail[j];        }    }}int main(){    while(~scanf("%d%d%d",&n,&k,&s))    {        for(int i = 1;i<=n;i++)            scanf("%d",&t.num[i]);        for(int i = 1;i<=k;i++)            scanf("%d",&p.num[i]);        t.len = n;        p.len = k;        //init(t);        init(p);        kmp();        printf("%d\n",ans.size());        for(int i = 0;i<ans.size();i++)            printf("%d\n",ans[i]);    }    return 0;}

还有一个枚举法,博客地址:http://www.mzry1992.com/blog/miao/%E3%80%90poj-3167%E3%80%91cow-patterns.html

依次递增枚举 p 中的数,然后匹配,整个过程都要保证匹配是递增的,复杂度O(s*s*n)。

好屌,这样都可以。。 这个方法,反正我是想不出来。。 = =
枚举代码如下:

#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;int n,k,s;int p[25005],t[111111];int tmp_p[25005],tmp_t[111111];int fail[25005];void get_fail(){    int j = 0;    fail[1] = 0;    for(int i = 2;i<=k;i++)    {        while(j >= 1 && tmp_p[i] != tmp_p[j+1]) j =fail[j];        if(tmp_p[i] == tmp_p[j+1]) j++;        fail[i] = j;    }}int tot;int cnt[111111],match[111111];void kmp(int cur){    for(int i = 1;i<=n;i++)        if(t[i] == cur) tmp_t[i] = 1;        else tmp_t[i] = 0;    int j = 0;    for(int i = 1;i<=n;i++)    {        while(j >= 1 && tmp_t[i] != tmp_p[j+1]) j = fail[j];        if(tmp_t[i] == tmp_p[j+1]) j++;        if(j == k)        {            int beg = i - j + 1;            if(match[beg] < cur && cnt[beg] <= tot)            {                cnt[beg] ++;                match[beg] = cur;            }            j = fail[j];        }    }}vector <int> ans;int occ[33];int main(){    while(~scanf("%d%d%d",&n,&k,&s))    {        memset(occ,0,sizeof(occ));        for(int i = 1;i<=n;i++) scanf("%d",&t[i]);        for(int i = 1;i<=k;i++) scanf("%d",&p[i]),occ[p[i]] = 1;        tot = 0;        memset(match,0,sizeof(match));        memset(cnt,0,sizeof(cnt));        for(int i = 1;i<=s;i++)        {            if(!occ[i]) continue;            for(int j = 1;j<=k;j++)                if(p[j] == i) tmp_p[j] = 1;                else tmp_p[j] = 0;            get_fail();            for(int j = 1;j<=s;j++) kmp(j);// p 中 i 和 t 中的 j 相匹配            tot++;        }        ans.clear();        for(int i = 1;i<=n;i++)        {            //printf("i = %d,cnt = %d\n",i,cnt[i]);            if(cnt[i] == tot)                ans.push_back(i);        }        printf("%d\n",ans.size());        for(int i = 0;i<ans.size();i++)            printf("%d\n",ans[i]);    }    return 0;}