2013-ACMICPC Nanjing Online 02 Parade Show

题目大意:

       给定n个人排成的一个队列,每个人身高为x[i](取值为1-k)。再给一个长度为m的模式串,问在原队列中最多能取出多少个匹配模式串的子队列。

       其中匹配是指两个串分别离散之后完全一样,即对任意i、j,若在模式串a中a[i]<a[j],那么在原串b中也要有b[i]<b[j],a[i]>a[j]、a[i]==a[j]同理。

          k(1<=k<=25),n (1<=n<=10^5) ,m(1<=m<=n)


解法:

    基础的想法是枚举出所有的匹配串,然后做区间调度的贪心出答案。这样就要解决2个问题。

       第一个是匹配的方式问题,怎么判断是否匹配。

       第二个是时间问题,m可能很大,所以匹配时候O(m)是不现实的,必须把匹配用时间尽量缩短。

       对于第一个问题我是这么解决的:用k个vector(记为num)记录模式串a(下面简称为a)中每一个数字出现的位置,num[i][j]就是模式串中数字i第j次出现的位置,这个可以在O(m)的时间预处理出来。

       这时满足匹配的条件是:对于模式串中的每个数字i,记这个数字在当前串中的对应数为Hash[num[i][0]]。那么对所有的i都要有Hash[num[i][0]]>Hash[num[j][0]](j是模式串中出现的比i小的上一个数字),并且对所有的k,都有Hash[num[i][k]] == Hash[num[i][0]]。

       那么事情就简单了,把num给预处理好之后,先判断Hash是否满足条件,这个最多做25次比较。此时如果Hash不满足条件,则表示不匹配继续往下走;而如果满足条件,则对每个数i判断是否Hash[num[i][k]] == Hash[num[i][0]],这中间如果发现不满足则跳出。那么匹配的问题就解决了。

       至于第二个问题,可以把贪心的思想加入到匹配的过程中来。区间调度的贪心思路是每次取可选的中结束最早的区间,而因为这题的区间长度都是m,结束最早的区间就是最早出现的匹配区间。也就是说,我匹配到一个区间之后就选这个区间,并且不再会选与这个区间相交的区间,从这个区间结束点开始继续匹配就好,也就是跳跃性进行匹配。

       这样可以达到的效果是1.如果是不匹配区间我可以很快跳出。2.如果是匹配区间我会一次往后跳跃m个位置。这样复杂度大概在O(n+若干个m),不是很好算,但总体来说会比较快。

 代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;

const int maxn = 100000+20;
int n,m,k;
int x[maxn];
int a[maxn];
int s[maxn],t[maxn];
vector<int> num[30];
int cnt[maxn][30];
int mo[30];
int Hash[30];
int main()
{
    while(~scanf("%d%d%d",&n,&m,&k)){
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<=k;i++){
            num[i].clear();
            mo[i]=0;
        }
        for(int i=0;i<n;i++){
            scanf("%d",&x[i]);
        }
        for(int i=0;i<m;i++){
            scanf("%d",&a[i]);
            mo[a[i]]++;
            num[a[i]].push_back(i);
        }
        for(int i=0;i<m;i++){
            cnt[0][a[i]]++;
        }
        for(int i=1;(i+m-1)<n;i++){
            for(int j=1;j<=k;j++){
                cnt[i][j] = cnt[i-1][j];
            }
            cnt[i][a[i-1]]--;
            cnt[i][a[i+m-1]]++;
        }
        int tot = 0;
        for(int i=0;(i+m-1)<n;){
            bool ok = 1;
            int bef = 0;
            Hash[0] = -1;
            //cout<<i<<"!  "<<endl;
            memset(Hash, -1, sizeof(Hash));
            for(int j=1;j<=k;j++){
                if(mo[j]>0){
                    //cout<<num[j].size()<<endl;
                    Hash[j] = x[num[j][0]+i];
                    if(Hash[j]>Hash[bef]){
                        bef = j;
                    }else{
                        ok = 0;
                        break;
                    }
                }
            }
            if(!ok){
                i++;
                continue;
            }
            for(int j=1;j<=k;j++){
                if(!mo[j])continue;
                for(int l=1;l<mo[j];l++){
                    if(x[i+num[j][l]] != Hash[j]){
                        ok = 0;
                        break;
                    }
                }
                if(!ok)break;
            }
            if(ok){
                tot ++;
                i = i+m-1;
            }
            i++;
        }
        printf("%d\n",tot);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值