题目链接
http://main.edu.pl/en/archive/oi/18/liz
题目大意
给你一个长度为
n
的序列
n,q≤106
思路
网上题解比较少,做法都是一样的:即大小为
x
的区间可行时,
这里我提出一种新的做法(感谢温州中学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的位置 n−Rone+1序列一定是这个样子:
22…221??…??122…22若 x>∑a[i]−2(min{Lone,Rone}−1) ,则表明选择的区间必须不得不选择 Lone 和 n−Rone+1 两边的2,若 xmod2=∑a[i]mod2 ,且 x≤sum ,则显然找得到可行区间
其他情况下,我们可以考虑在 [Lone+1,n] 区间进行二分,以及在 [1,n−Rone] 区间进行二分找出一个位置 t ,使得
x−1≤∑ti=Lone+1≤x ,或者 x−1≤∑n−Ronei=t≤x ,则找得到可行区间
显然这样的二分做法是正确的,因为只要存在合法区间,就肯定可以通过二分,在最左边或最右边的1旁边找出连续的一段和,恰好等于 x 或x−1 ,如果是 x−1 的话,就把那个1补上去就成了 x (序列里只包含1或者2,因此只要存在合法区间,二分后就得不到一个x−3,x−4... 大小的区间)PS:若 ∑ti=Lone+1=x ,或者 ∑n−Ronei=t=x ,则答案区间就是 [Lone+1,t] ( [t,n−Rone] );若 ∑ti=Lone+1=x−1 ,或者 ∑n−Ronei=t=x−1 ,则答案区间就是 [Lone,t] ( [t,n−Rone+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;
}