peiwenjun's blog 没有知识的荒原

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 当成 +1B 当成 -1 ,枚举 X 的位置,统计 A 位置前缀和为正的数目。

考虑每次将 X\(i-1\) 移到 \(i\) ,前缀和数组的变化。

初始把所有不是 A 的位置都当成 B

我们认为 X 的存在仅仅是 "掩盖" 了一个(全局)前缀和,但不会对数组造成影响。

比如原数组为 ABBAB ,当 X 位于第三个位置时,需要考虑的字符串(从第四个字符开始)为 A,AB,ABA,ABAB ,但 ABABBX 掩盖了。其中只有 A,ABAA 结尾,所以需要被统计。

手玩发现,我们需要删除前缀和 \([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;
}

posted on 2023-01-11 18:49  peiwenjun  阅读(6)  评论(0)    收藏  举报

导航