AtCoder Regular Contest 127
阅读原文时间:2021年11月18日阅读:1

Portal

Description

给出\(n(\leq5\times10^4),L(\leq15)\),构造\(3n\)个不同\(L\)位的三进制数,使得在这\(3n\)个数的每一位上,0/1/2各出现\(n\)次。在这样的前提下,使得其中的最大数尽可能小。

Solution

易知最大的\(n\)个数一定是2开头的,那么就令这\(n\)个数为\(200..0_{(3)},200..0_{(3)}+1,…,200..0_{(3)}+n-1\)。

将这些数中的0换成1,1换成2,2换成0,作为最小的\(n\)个数;将这些数中的0换成2,1换成0,2换成1,作为中间的\(n\)个数。

C

Description

对于无前缀零的\(1..2^n(n\leq10^6)\)这些二进制数,将其作为字符串按字典序排列,求第\(x(\leq2^n-1)\)个(\(x\)以二进制给出)。

Solution

考虑这个排列是怎么生成的。按位数将二进制数加入到排列中(新加入的用[]标注):

  • 1位:[1]
  • 2位:1 [10 11]
  • 3位:1 10 [100 101] 11 [110 111]
  • 4位:1 10 100 [1000 1001] 101 [1010 1011] 11 110 [1100 1101] 111 [1110 1111]

发现\(i\)位数都是在\(i-1\)位数后插入两个,那么除第一位为1外,一个序列可以分成:一个空串 + \(2^k-1\)个0首串 + \(2^k-1\)个1首串。于是可以递归求解。第\(x\)个串(从0开始)是:

  • 空串,当\(x=0\)。
  • 0首串中的第\(x-1\)个,当\(x<2^k\)。
  • 1首串中的第\(x-2^k\)个,当\(2^k \leq x\)。

递归至多\(n\)次,便可确定每一位的取值。你或许会担心对大数\(x\)进行运算会让复杂度退化到\(O(n^2)\),不过其实是不会的。

判断\(x\)与\(2^k\)的大小只要观察\(x\)的首位;\(x-2^k\)只需移除首位上的1。对于判0操作,可以维护\(x\)中1的数目,若\(x\)中没有1说明\(x=0\)。对于\(x-1\)操作,寻找到最后的1位,将其置0并将后面所有位置1,这一过程中可以维护\(x\)中1的数目。由于\(x-1\)操作最多执行\(n\)次,而第\(k\)位每\(2^k\)次操作中才会被借位一次,且一经借位后方都被置1,使得借位的复杂度大大降低。

Code

//Binary Strings
#include <cstdio>
#include <cstring>
const int N=1e6+10;
int n; char x[N],y[N];
bool equal0()
{
    for(int i=n;i>=1;i--) if(x[i]=='1') return false;
    return true;
}
void minus1()
{
    int k=n;
    while(x[k]=='0') k--;
    x[k]='0';
    for(int i=k+1;i<=n;i++) x[i]='1';
}
int main()
{
    scanf("%d",&n);
    scanf("%s",x+1);
    int m=strlen(x+1);
    for(int i=n;i>=1;i--) x[i]=(i-n+m>0)?x[i-n+m]:'0';
    for(int i=1;i<=n;i++) y[i]=0;
    minus1();
    y[1]='1';
    for(int k=1;k<=n;k++)
    {
        if(equal0()) break;
        if(x[k]=='0') y[k+1]='0',minus1();
        else y[k+1]='1',x[k]='0';
    }
    puts(y+1);
    return 0;
}