P3770 [CTSC2017]密钥 题解
题目描述
一个长为 \(n=2k+1\) 的字符串在环上顺时针排列,由 \(1\) 个 X 、 \(k\) 个 A 、 \(k\) 个 B 组成。
一个字母 A 被称为 "强的" ,当且仅当从 X 沿顺时针走到这个 A ,经过的 A 的数量严格比 B 多。
一个密钥的特征值为 "强的" A 的个数。
给定 \(k,S\) ,你需要解决下面三个问题:
- 给定密钥中所有
A的位置,若特征值为零,求X的位置。 - 给定密钥中所有
A的位置,若特征值为 \(S\) ,求X的位置。 - 给定密钥中所有
B的位置,若特征值为 \(S\) ,求X的位置。
数据范围
- \(0\le S\le k\le 10^7\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{500MB}\) 。
分析
先考虑暴力怎么做。
把 A 当成 +1 , B 当成 -1 ,枚举 X 的位置,统计 A 位置前缀和为正的数目。
考虑每次将 X 从 \(i-1\) 移到 \(i\) ,前缀和数组的变化。
初始把所有不是
A的位置都当成B。我们认为
X的存在仅仅是 "掩盖" 了一个(全局)前缀和,但不会对数组造成影响。比如原数组为
ABBAB,当X位于第三个位置时,需要考虑的字符串(从第四个字符开始)为A,AB,ABA,ABAB,但ABABB被X掩盖了。其中只有A,ABA以A结尾,所以需要被统计。
手玩发现,我们需要删除前缀和 \([i,i]\) 的贡献,加入全局前缀和 \([i,i-1]\) 的贡献,再将所有前缀和减去字符 \(p_i\) 的贡献。
要求支持全局 +1,-1 ,单点修改,统计正数个数,维护一个桶即可。
对于第三问,当然可以按照上面的方法再写一遍,寻找 X 使得前缀和为负的 B 个数恰好为 \(S\) ,但是没必要。
我们可以将给定的位置视为 A ,要求特征值为 \(k-S\) 。
这是因为前缀和 \(\gt 0\) 的 +1 个数与前缀和 \(\lt 0\) 的 -1 个数等于 \(k\) 。
第三问限制后者数量为 \(S\) ,因此前者数量为 \(k-S\) 。
时间复杂度 \(\mathcal O(n)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e7+5;
int k,n,s,cnt,seed;
int p[maxn],num[3],res[3];
int tmp[maxn],*a=tmp+maxn/2;
int getrand()
{
seed=((seed*12321)^9999)%32768;
return seed;
}
void init()
{
int t=0;
for(int i=1;i<=n;i++) p[i]=getrand()>>7&1,t+=p[i];
for(int i=1;i<=n;i++)
{
if(t>k&&p[i]) p[i]=0,t--;
if(t<k&&!p[i]) p[i]=1,t++;
}
}
int main()
{
scanf("%d%d%d",&k,&seed,&s),n=2*k+1;
num[1]=s,num[2]=k-s,init();
for(int i=2,cur=0;i<=n;i++)
{
cur+=p[i]?1:-1,a[cur]+=p[i];
cnt+=cur>0&&p[i];
}
if(!p[1]) for(int j=0;j<=2;j++) if(cnt==num[j]) res[j]=1;
for(int i=2;i<=n;i++)
{
cnt-=p[i],a[p[i]]-=p[i],a[-1]+=p[i-1];
if(p[i]) cnt-=a[1],a++;
else
{
cnt+=a[0],a--;
for(int j=0;j<=2;j++) if(cnt==num[j]) res[j]=i;
}
}
for(int j=0;j<=2;j++) printf("%d\n",res[j]);
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/17044663.html
浙公网安备 33010602011771号