题目大意:
题目链接:https://www.luogu.org/problemnew/show/P3514
给一个只有1和2的序列,每次询问有没有一个子串的和为
x
x
x
思路:
思维题。
如果序列
[
l
,
r
]
[l,r]
[l,r]之和为
k
k
k,那么
- 当 a [ l ] = 2 a[l]=2 a[l]=2时, ∑ i = l + 1 r a [ i ] = k − 2 \sum^{r}_{i=l+1}a[i]=k-2 ∑i=l+1ra[i]=k−2
- 当 a [ r ] = 2 a[r]=2 a[r]=2时, ∑ i = l r − 1 a [ i ] = k − 2 \sum^{r-1}_{i=l}a[i]=k-2 ∑i=lr−1a[i]=k−2
- 当 a [ l ] = a [ r ] = 1 a[l]=a[r]=1 a[l]=a[r]=1时, ∑ i = l + 1 r − 1 a [ i ] = k − 2 \sum^{r-1}_{i=l+1}a[i]=k-2 ∑i=l+1r−1a[i]=k−2
所以,如果有一段自区间
[
l
,
r
]
[l,r]
[l,r]之和为
k
k
k,那么必然有一段区间的和为
k
−
2
k-2
k−2。
归纳得,如果一段区间之和为
k
k
k,那么必然有区间之和为k' (k与k'奇偶性相同)
所以分类来讨论,找到区间之和最大的奇数和偶数,然后比他们小的数字都可以得到。预处理出每一个数的区间即可。
这样离线的时间复杂度就是
O
(
m
log
n
)
O(m\log n)
O(mlogn)了。
代码:
#include <cstdio>
using namespace std;
const int N=1000010;
int n,m,L,R,sum,sum1,sum2,a[N],l[N*2],r[N*2];
char ch;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
while (ch=getchar()) if (ch=='W'||ch=='T') break;
if (ch=='W') a[i]=1;
else a[i]=2;
sum+=a[i]; //求和
}
l[sum]=1; r[sum]=n;
L=1; R=n;
for (int i=sum-2;i>0;i-=2)
{
if (a[L]==2) L++;
else if (a[R]==2) R--;
else L++,R--; //3种转移方式
l[i]=L,r[i]=R; //记录答案
}
sum1=sum2=sum;
for (L=1;a[L]==2;L++) sum1-=2;
for (R=n;a[R]==2;R--) sum2-=2;
sum1--; sum2--; L++; R--; //找到最大奇数(偶数)
if (sum1>sum2)
{
l[sum1]=L,r[sum1]=n;
R=n; sum=sum1;
}
else
{
l[sum2]=1,r[sum2]=R;
L=1; sum=sum2;
}
for (int i=sum-2;i>0;i-=2)
{
if (a[L]==2) L++;
else if (a[R]==2) R--;
else L++,R--;
l[i]=L,r[i]=R;
}
int x;
while (m--)
{
scanf("%d",&x);
if (!l[x]) printf("NIE\n");
else printf("%d %d\n",l[x],r[x]);
}
return 0;
}