hdu 4749 Parade Show KMP

题目意思,给你两个串,让你求出第一个串中有多少个子串,这个子串和第二个串满足:任意一个数字在其中的相对位置都一样。 串与串不能重叠,求最多有多少个。


其实就是给你一个文本串一个模式串求有多少个匹配的问题,典型的KMP,但这里所谓的“匹配”,不是指两个数字相同,而是指两个数字在其中相对位置等价。

由此可以想到,我们可以用一个函数,代替KMP中 s[i]!=p[i]这条语句,而这个函数,就是判断两个数字的相对位置等价。


什么是相对位置?就是这个数字前面有多少数字比它小,还有,前面有多少个数字和它相等。如果两个相对位置都一样,他们就可以匹配。(一定要两者分开讨论,不能直接看<=的数字是否相等,我在这WA了一晚上!!!)。

如何快速求这个数字前面有多少数字比它小呢?首先可以用树状数组求逆序数的思想,求出,由于此题的范围很小,只有25,所以可以用基数排序,统计

<=i位置一共有多少个1,2,3,4...k。这样的话,求小于X的数,就从1累加到X-1就够了。

注意next数组的匹配和KMP的匹配稍有不同。


此题的关键就在于KMP中的两个匹配函数。


#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define INF 1000000000
#define MAXN 100005
int vis[30];
int next[MAXN];
int s[MAXN],p[MAXN];
int c1[30][MAXN],c2[30][MAXN];
int cal_s(int pos,int k)
{
    if(pos<0) return 0;
    int s=0;
    for(int i=1;i<k;i++) s+=c1[i][pos];
    return s;
}
int cal_p(int pos,int k)
{
    if(pos<0) return 0;
    int s=0;
    for(int i=1;i<k;i++) s+=c2[i][pos];
    return s;
}
bool isok(int i,int j)
{
    if(!j) return true;
    if (c2[p[j]][j]!=c1[s[i]][i]-c1[s[i]][i-j-1]) return false;
    int cnt1=cal_s(i,s[i]);
    int cnt2=cal_s(i-j-1,s[i]);
    int cnt3=cal_p(j,p[j]);
    return cnt1-cnt2==cnt3;
}
bool ok(int i,int j)
{
    if(!j) return true;
    if (c2[p[j]][j]!=c2[p[i]][i]-c2[p[i]][i-j-1]) return false;
    int cnt1=cal_p(i,p[i]);
    int cnt2=cal_p(i-j-1,p[i]);
    int cnt3=cal_p(j,p[j]);
    return cnt1-cnt2==cnt3;
}
void getnext(int n)
{
    bool flag;
    for(int i=1;i<n;i++)
    {
        int j=next[i];
        flag=ok(i,j);
        while(j&&!flag)
        {
            j=next[j];
        }
        if(flag)
        {
            next[i+1]=j+1;
        }
        else
        {
            next[i+1]=0;
        }
    }
}
int ans;
int kmp(int n,int m)
{
    getnext(m);
    bool flag;
    int j=0;
    for(int i=0;i<n;i++)
    {
        flag=isok(i,j);
        while(j&&!flag) {j=next[j];flag=isok(i,j);}
        if(flag) j++;
        if(j==m)
        {
            ans++;
            j=0;
        }
    }
    return -1;
}
inline int ReadInt()
{
    int flag=0;
    char ch = getchar();
    int data = 0;
    while (ch < '0' || ch > '9')
    {
        if(ch=='-') flag=1;
        ch = getchar();
    }
    do
    {
        data = data*10 + ch-'0';
        ch = getchar();
    }while (ch >= '0' && ch <= '9');
        if(flag) data=-data;
        return data;
}
int main()
{
    int n,m,kk;
    while(~scanf("%d%d%d",&n,&m,&kk))
    {
        ans=0;
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
        for(int i=0;i<n;i++)
        {
            s[i]=ReadInt();
            if(i){
            for(int j=1;j<=kk;j++){
                c1[j][i]=c1[j][i-1];}
            }
            c1[s[i]][i]++;
        }

        for(int i=0;i<m;i++)
        {
            p[i]=ReadInt();
            if(i)
            for(int j=1;j<=kk;j++)
                c2[j][i]=c2[j][i-1];
            c2[p[i]][i]++;
        }
        getnext(m);
        kmp(n,m);
        cout<<ans<<endl;

    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TommyTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值