poj3167 Cow Patterns【KMP】

题目大意:

约翰的N(1≤N≤100000)只奶牛中出现了K(1≤K≤25000)只爱惹麻烦的坏蛋。奶牛们按一定的顺序排队的时候,这些坏蛋总会站在一起。为了找出这些坏蛋,约翰让他的奶牛排好队进入牛棚,同时需要你的慧眼来识别坏蛋,为了区分,约翰给所有奶牛都发了号牌,上面写着一个1~S(1≤S≤25)之间的数字。虽然这不是一个完美的方法,但也能起一点作用。现在,约翰已经不记得坏蛋们的具体号码。但是凭他的记忆,他给出一个“模式串”。原坏蛋的号码如果相同,模式串中他们的号码依然相同。模式串中坏蛋们之间号码的大小关系也与原号码相同的。比如,对于这样一个模式串:1,4,4,3,2,1。原来的6只坏蛋,排最前面的与排最后的号码相同(尽管不一定是1),而且他们的号码在团伙中是最小的。第2,3位置的坏蛋,他们的号码也相同(不一定是4),且是坏蛋团伙中最大的。现在所有奶牛排成队列,号码依次是这样:5,6,2,10,10,7,3,2,9存在子串2,10,10,7,3,2,满足模式串的相同关系和大小关系,所以这就是坏蛋团伙,请找出K个坏蛋的困伙的所有可能性。

解题思路:

题目很容易让人想到kmp,但如何判断两个字符串匹配呢?
我们发现,只要判断当前位置在已匹配的部分中大小排名相同即可。
所以只要把kmp中的a[i]==b[j+1]改为count(a[i])==count(b[j+1]),即a[i-j+1]~a[i]中小于a[i]的数目等于b[1]~b[j+1]中小于b[j+1]的数目,且与a[i]、b[j+1]相等的数目也相等即可。
字符集很小,用前缀和优化就行了,大了的话可以考虑树状数组。

#include<cstdio>
#include<vector>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<1)+(i<<3)+c-'0';
    return i*f;
}

const int N=100005;
int n,m,s,a[N],b[N],suma[30][N],sumb[30][N],nxt[N];
vector<int> ans;

int count_a(int ch,int l,int r)
{
    int res=0;
    for(int i=1;i<ch;i++)res+=suma[i][r]-suma[i][l-1];
    return res;
}

int count_b(int ch,int l,int r)
{
    int res=0;
    for(int i=1;i<ch;i++)res+=sumb[i][r]-sumb[i][l-1];
    return res;
}

bool check1(int x,int y)
{
    if(count_b(b[x],1,x)!=count_b(b[y],y-x+1,y))return false;
    return sumb[b[x]][x]==sumb[b[y]][y]-sumb[b[y]][y-x];
}

bool check2(int x,int y)
{
    if(count_b(b[x],1,x)!=count_a(a[y],y-x+1,y))return false;
    return sumb[b[x]][x]==suma[a[y]][y]-suma[a[y]][y-x];
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),m=getint(),s=getint();
    for(int i=1;i<=n;i++)
    {
        a[i]=getint();
        for(int j=1;j<=s;j++)
            suma[j][i]=suma[j][i-1]+(a[i]==j);
    }
    for(int i=1;i<=m;i++)
    {
        b[i]=getint();
        for(int j=1;j<=s;j++)
            sumb[j][i]=sumb[j][i-1]+(b[i]==j);
    }
    for(int i=2,j=0;i<=m;i++)
    {
        while(j&&!check1(j+1,i))j=nxt[j];
        if(check1(j+1,i))j++;
        nxt[i]=j;
    }
    for(int i=1,j=0;i<=n;i++)
    {
        while(j&&!check2(j+1,i))j=nxt[j];
        if(check2(j+1,i))j++;
        if(j==m)ans.push_back(i-m+1);
    }
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();i++)printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值