[POI 2011]Lollipop(二分+特判)

23 篇文章 0 订阅
8 篇文章 0 订阅

题目链接

http://main.edu.pl/en/archive/oi/18/liz

题目大意

给你一个长度为 n 的序列a[],序列里只包含数字1或2, q 次询问一个数字x,问序列里是否存在一个连续的区间 [L,R] ,使得 Ri=La[i]=x ,并输出一个可行的区间。

n,q106

思路

网上题解比较少,做法都是一样的:即大小为 x 的区间可行时,x2大小的区间也是可行的,因此可以离线回答所有询问,分奇数和偶数的 x 分开讨论,先找出最大的奇数和偶数的x以及一个可行区间,然后倒着推 x2,x4... 的可行区间

这里我提出一种新的做法(感谢温州中学au大爷林一诺提供的思路)。

我们对序列里包含的1的个数不超过1和大于1分开讨论:
1、1的个数小于等于1

在这个情况下,我们用特判来回答每个询问,总的复杂度 O(q)

如果1的个数为0,那么询问的 x 必须a[i],且 2|x

如果1的个数为1,若 xmod2=a[i]mod2 ,则询问的 x 必须a[i];否则说明询问的 x 是个偶数,必须只由2组成,看1左边的2之和和1右边的2之和分别是否大于等于x(保证 x 能由1左边的2或者1右边的2里找出一个子区间凑出)

2、1的个数大于1

我们找出这个序列里离左端点最近的1的位置Lone和离右端点最近的1的位置 nRone+1

序列一定是这个样子:
22…221??…??122…22

x>a[i]2(min{Lone,Rone}1) ,则表明选择的区间必须不得不选择 Lone nRone+1 两边的2,若 xmod2=a[i]mod2 ,且 xsum ,则显然找得到可行区间

其他情况下,我们可以考虑在 [Lone+1,n] 区间进行二分,以及在 [1,nRone] 区间进行二分找出一个位置 t ,使得x1ti=Lone+1x,或者 x1nRonei=tx ,则找得到可行区间
显然这样的二分做法是正确的,因为只要存在合法区间,就肯定可以通过二分,在最左边或最右边的1旁边找出连续的一段和,恰好等于 x x1,如果是 x1 的话,就把那个1补上去就成了 x (序列里只包含1或者2,因此只要存在合法区间,二分后就得不到一个x3,x4...大小的区间)

PS:若 ti=Lone+1=x ,或者 nRonei=t=x ,则答案区间就是 [Lone+1,t] ( [t,nRone] );若 ti=Lone+1=x1 ,或者 nRonei=t=x1 ,则答案区间就是 [Lone,t] ( [t,nRone+1] )

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 2100000

using namespace std;

int n,m,a[MAXN],cnt[3],sum=0; //cnt[1]=1的个数,cnt[2]=2的个数
char s[MAXN];

namespace SpecialCheck //只有一个1,特判
{
    void solve()
    {
        int pos=-1; //pos=唯一的那个1的位置
        for(int i=1;i<=n;i++)
            if(a[i]==1)
            {
                pos=i;
                break;
            }
        for(int i=1;i<=m;i++)
        {
            int x;
            scanf("%d",&x);
            if(x>sum)
            {
                printf("NIE\n");
                continue;
            }
            if((sum%2)==0&&(x%2)!=(sum%2))
            {
                printf("NIE\n");
                continue;
            }
            if((sum%2)==1&&(x%2)==0)
            {
                if(2*(pos-1)<x&&2*(n-pos)<x)
                {
                    printf("NIE\n");
                    continue;
                }
            }
            if((sum%2)==0)
            {
                printf("1 %d\n",x/2);
                continue;
            }
            //sum%2==1
            if(x%2==0)
            {
                if(x/2<pos)
                {
                    printf("1 %d\n",x/2);
                    continue;
                }
                printf("%d %d\n",n-x/2+1,n);
            }
            else
            {
                int need=x/2; //需要数字2的个数
                int L=max(1,pos-need); //选取的区间左端点
                int R=L+need;
                printf("%d %d\n",L,R);
            }
        }
    }
}

namespace BinarySearchEdition //二分版
{
    int presum[MAXN],sufsum[MAXN]; //前缀和、后缀和
    void solve()
    {
        int Lone,Rone; //最左边和最右边的1的位置
        for(int i=1;i<=n;i++)
            if(a[i]==1)
            {
                Lone=i;
                break;
            }
        for(int i=n;i>=1;i--)
            if(a[i]==1)
            {
                Rone=n-i+1;
                break;
            }
        for(int i=1;i<=n;i++) presum[i]=presum[i-1]+a[i];
        for(int T=1;T<=m;T++)
        {
            int x;
            scanf("%d",&x);
            if(x==1)
            {
                printf("%d %d\n",Lone,Lone);
                continue;
            }
            if(x>sum-2*(min(Lone,Rone)-1))
            {
                if((x%2)!=(sum%2))
                    printf("NIE\n");
                else if(x>sum)
                    printf("NIE\n");
                else
                {
                    int need=(x-(sum-2*(Lone+Rone-2)))/2; //需要的2的个数
                    int L=max(1,Lone-need);
                    int R=n-Rone+1+(need-(Lone-L));
                    printf("%d %d\n",L,R);
                }
                continue;
            }

            int lowerBound=Lone+1,upperBound=n,ans=-1;
            while(lowerBound<=upperBound)
            {
                int mid=(lowerBound+upperBound)>>1;
                if(presum[mid]-presum[Lone]<=x)
                {
                    ans=mid;
                    lowerBound=mid+1;
                }
                else upperBound=mid-1;
            }
            if(ans!=-1)
            {
                if(presum[ans]-presum[Lone]==x)
                {
                    printf("%d %d\n",Lone+1,ans);
                    continue;
                }
                else if(presum[ans]-presum[Lone]==x-1)
                {
                    printf("%d %d\n",Lone,ans);
                    continue;
                }
            }
            lowerBound=1,upperBound=n-Rone,ans=-1;
            while(lowerBound<=upperBound)
            {
                int mid=(lowerBound+upperBound)>>1;
                if(presum[n-Rone]-presum[mid-1]<=x)
                {
                    ans=mid;
                    upperBound=mid-1;
                }
                else lowerBound=mid+1;
            }
            if(ans!=-1)
            {
                if(presum[n-Rone]-presum[ans-1]==x)
                {
                    printf("%d %d\n",ans,n-Rone);
                    continue;
                }
                else if(presum[n-Rone]-presum[ans-1]==x-1) //!!!!!
                {
                    printf("%d %d\n",ans,n-Rone+1);
                    continue;
                }
            }
            printf("NIE\n");
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++) a[i]=s[i]=='T'?2:1,cnt[a[i]]++,sum+=a[i];
    if(cnt[1]<=1)
    {
        using namespace SpecialCheck;
        solve();
    }
    else
    {
        using namespace BinarySearchEdition;
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值