bzoj4900【CTSC2017】Day1T1 密钥 cipher 乱搞

题目大意:
有一个包含n个点(n=2*k+1)的环(密钥),环上有k个点为A,有k个点为B,剩下一个点为X。
对于每一个A,从X顺时针走到这个A,如果途中A的数目严格多于B的数目,那么这个A为强的。一个密钥的特征值为强的A的个数。
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4900

有三个问题:
1、固定所有A的位置,求特征值为0时,X的位置。
2、固定所有A的位置,求特征值为S时,X的位置。
3、固定所有B的位置,求特征值为S时,X的位置。

题目分析:

先扯一会儿淡、
说实话听题解的时候我是一脸懵逼的=。=
大佬你在讲什么玄学的东西QAQ
为毛要转成括号?为毛为毛?
为毛又搞到树上了?为毛为毛?
考试的时候就想到了O(n)的做法,但是为毛就是没调出来?为毛为毛?
而且出来的时候有一堆大佬说用后缀数组就可简单可简单的能拿到多少多少分啊……现在我也不知道后缀数组要怎么做。
CTSC滚粗orz。

考察知识点:在循环结构中深入应用数组以及函数的构造与应用。
其实就是乱搞orz。

我们随便写一个满足题意的序列。
XBBAABA
我们知道一个B可以抵消掉一个A的贡献,那么我们把X作为初始位置,给所有的B赋值为-1,所有的A赋值为1。那么一个A为强的当且仅当这个A所在位置的前缀和大于0。

那上面写的序列举例:
X B B A A B A
0-1-2-1 0-1 0 (前缀和)
这个序列中前缀和大于0的A的个数为0,所以这个序列的特征值为0。

由于题中给了X处于每一个位置时的特征值严格取遍0~k,那么我们其实只要快速算出每一个位置的特征值,然后找到题目中要求的特征值的位置即可。

那么我们一开始可以随意找一个位置放置X,然后O(n)求出当前序列的特征值,顺道处理一下每个A位置的前缀和。

之后我们可以考虑每次让X与后面第一个B交换。
假设当前X的位置与后面第一个B之间有x个A。
那我们的这个操作相当于:
1、在序列首端删去x个A;
2、在序列首端删去一个B;
3、在序列尾端填上一个B;
4、在序列尾端填上x个A。

我对于当前序列的特征值记为num。

对于1、2两个操作,就相当于是每次删掉一个数,然后对整个序列进行+1或者-1。然而我们不可能把每个数真的+1或者-1,那么我们可以调整“0”的值。我们原来要求的是前缀和大于0的A有多少个,把这个序列整体+1,相当于把“0”的值-1,就是说这个问题转化成了【原】序列有多少个前缀和大于-1的数。

num原本记录的是大于0的A有多少个,现在询问大于-1的A有多少个,只需要在原num的基础上加上前缀和为-1的A的个数。

这样我们再开一个数组sum,sum[i]记录前缀和为i的A的个数。
sum数组只需要在添加一个A和删除一个A的时候操作。

1、2操作就可以这样处理。
3操作不影响答案所以不用管它。

对于4操作,在末尾增添x个A,我们要把在序列尾填上一些数。
我们不能重新计算前缀和,但是我们可以发现,序列的最后一项前缀和一定为0(因为前面所有的A和B都抵消了),这样我们从序列尾倒着加回去,就知道每一项的值了,改一下sum数组即可。

这样可以在把这个序列扫一遍的时间内算出所有的每个位置的num值,当得到答案所求的num值时即可输出答案。

时间复杂度是O(n)。

第一问和第二问都可以这么搞,第三问的话仿照第一问和第二问的过程自己推一下,都是差不多的。

代码如下:

#include <cstdio>
#include <cstring>
#define N 20000005
using namespace std;
int p[N];
int sum[N],a[N];
bool b[N];
int seed,n,k,S;
int getrand(){
    seed=((seed*12321)^9999)%32768;
    return seed;
}
void generateData()
{
    scanf("%d%d%d",&k,&seed,&S);
    int t=0;
    n=k*2+1;
    for(int i=1;i<=n;i++)
    {
        p[i]=(getrand()/128)&1;
        t+=p[i];
    }
    int i=1;
    while(t>k)
    {
        while(p[i]==0) i++;
        p[i]=0;
        t--;
     }
     while(t<k)
     {
         while(p[i]==1) i++;
         p[i]=1;
         t++;
     }
}
void A(int S)
{
    memset(sum,0,sizeof(sum));
    int zero=k,last=zero,now=1,st,num=0,tmp=0;
    while(p[now]) now++;
    st=now; now++; if(now>n) now=1;
    while(now!=st)
    {
        if(p[now])
        {
            a[now]=++last;
            sum[a[now]]++;
            if(a[now]>zero) num++;
        }
        else a[now]=--last;
        now++;
        if(now>n) now=1;
    }
    while(num!=S || (num==S && p[now]))
    {
        now++;
        if(now>n) now=1;
        if(p[now])
        {
            zero++;
            num-=sum[zero];
            sum[zero]--;
            tmp++;
        }
        else
        {
            num+=sum[zero];
            zero--;
            for(int i=0;i<tmp;i++)
                sum[zero-i]++;
            tmp=0;
        }

    }
    printf("%d\n",now);
}
void B(int S)
{
    memset(sum,0,sizeof(sum));
    int zero=k,last=zero,now=1,st,num=0,tmp=0;
    while(p[now]) now++;
    st=now; now++; if(now>n) now=1;
    while(now!=st)
    {
        if(p[now]) --last;
        else
        {
            a[now]=++last;
            sum[a[now]]++;
            if(a[now]>zero) num++;
        }
        now++;
        if(now>n) now=1;
    }
    while(num!=S || (num==S && p[now]))
    {
        now++;
        if(now>n) now=1;
        if(p[now])
        {
            num+=sum[zero];
            zero--;
            tmp++;
        }
        else
        {
            zero++;
            num-=sum[zero];
            sum[zero]--;
            sum[zero+tmp]++;
            if(tmp>0) num++;
            tmp=0;
        }
    }
    printf("%d\n",now);
}
int main()
{
    freopen("cipher.in","r",stdin);
    freopen("cipher.out","w",stdout);
    generateData();
    A(0);
    A(S);
    B(S);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章