bzoj3048: [Usaco2013 Jan]Cow Lineup(单调队列)

6 篇文章 0 订阅
3 篇文章 0 订阅

题目传送门
啥话都不想说快排的判断写错了!!

解法:
题目要求连续的一段最长(大概是这样)
其实就是要求原序列中最长的一段。使得里面的数不超过K+1种。
比如:
K=2 也就是要删除两种数。
现在有个区间为555552
有一个区间为555521。
很显然上面那个更优。
但是我们要求在原序列里最长。
所以肯定会有一段为
555552?或者?555552
使得不超过三种数。

然后就用单调队列来维护咯。我不知道算不算是单调队列。
每次进来一个点最多就只有两种情况
第一种:
进来的点在队列里已经出现过,那么直接数量+1就行。
第二种:
进来的点没在队列里出现过,如果不超过K+1种,那么直接加进去就好。
如果进来了新的点之后超过了K+1种,那么从队头删,直到删除剩下K+1种就好。

答案维护的话我本来想打个线段树的。
但是每进来或删除一个点,对答案的影响只有0或1。所以用线段树就显得没必要了。
直接O(1)维护就OK

代码实现:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
struct node {
    int x,s,z;
}A[1110000],b[1110000];
int cmp(const void *xx,const void *yy) {
    node n1=*(node *)xx;
    node n2=*(node *)yy;
    return n1.x-n2.x;
}
int a[1100000];
int list[1110000];
int s[1110000],ys[1110000];
//我用数组来存它的值,可是10的9次方数组存不下我把它离散化了一下。
int main() {
    freopen("lineup_gold.in","r",stdin);
    freopen("lineup_gold.out","w",stdout);
    int n,k;scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) {
        scanf("%d",&A[i].x);
        A[i].s=i;
    }
    for(int i=1;i<=n;i++) {
        b[i].x=A[i].x;b[i].s=i;
        //x表示原来的值,s表示原来的位置
    }
    qsort(b+1,n,sizeof(node),cmp);
    b[1].z=1; //z表示离散化后的值
    for(int i=2;i<=n;i++) {
        if(b[i].x==b[i-1].x)
            b[i].z=b[i-1].z;
        else
            b[i].z=b[i-1].z+1;
    }
    for(int i=1;i<=n;i++)
        a[b[i].s]=b[i].z;
    int head=1,tail=1;
    memset(s,0,sizeof(s));
    memset(ys,0,sizeof(ys));
    //s表示i出现多少次,ys表示出现i次的有多少个。
    int mx=0,ans=0,t=0;  //mx表示为当前队列里的最优答案。ans表示为全局的最优答案。t表示当前队列有多少种。
    for(int i=1;i<=n;i++) {
        if(s[a[i]]>0) { //如果这个出现过
            ys[s[a[i]]]--; //因为s[a[i]]++了,所以原来出现过这个次数的就要-1
            s[a[i]]++;mx=max(mx,s[a[i]]); //维护答案
            ys[s[a[i]]]++;
            list[tail++]=a[i]; //加进队列里
            ans=max(ans,mx);
        }
        else {
            t++;  //这个种类之前没出现过,所以种类数+1
            while(head<tail&&t>k+1) { //如果种类>k+1
                int x=list[head];
                if(s[x]==1) { //如果这个数只剩一个了。
                    if(mx==1&&ys[1]==1)  //如果答案刚好等于这个,而且出现1次的数又只有一个,那么删掉这个数就对答案有影响咯。
                        mx--;
                    t--;//种数也要-1
                    ys[s[x]]--;
                    s[x]--;ys[s[x]]++;
                    mx=max(mx,s[x]);
                }
                else {
                    if(mx==s[x]&&ys[s[x]]==1)
                        mx--;  //如果答案等于这个数出现的次数而又只有一个数的出现次数等于答案所以答案-1,因为我们删了这个数。
                    ys[s[x]]--;
                    s[x]--;
                    ys[s[x]]++;
                    mx=max(mx,s[x]);
                }
                head++;
            }
            s[a[i]]++;mx=max(mx,s[a[i]]);
            ys[s[a[i]]]++;
            list[tail++]=a[i]; //加进队列。
            ans=max(ans,mx);
        }
    }
    printf("%d\n",ans);
    return 0;
}

不错的题我的快拍都打错了真是辣鸡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值