AtCoder Regular Contest 127

Portal

B

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;
}

D - Sum of Min of Xor

Description

给出两个数组\(a_{1..n},b_{1..n}\),求\(\sum_{1\leq i\leq j\leq n}min(a_i⊕a_j,b_i⊕b_j)\)。其中\(n\leq2.5\times10^5,a_i,b_i\leq2^{18}\)

Solution

按位分治。

对于第\(k\)位,将\(1..n\)划分为四个集合\(S_{00},S_{01},S_{10},S_{11}\),其中\(i\in S_{pq}\iff a_i[k]=p,b_i[k]=q\)\([k]\)表示二进制第\(k\)位)。

按位计算贡献,\(min\)\(a_i⊕a_j\)\(b_i⊕b_j\)的首个差异位做出选择。

\(i,j\)同属一个集合,那么\(min(a_i⊕a_j,b_i⊕b_j)\)在第\(k\)位上必为0,没有贡献。除此之外共有六种情况:

  • \(i\in S_{00},j\in S_{01} \rightarrow a_i⊕a_j=0,b_i⊕b_j=1\)\(min\)选择\(a_i⊕a_j\),计算这些\((i,j)\)的贡献,停止分治。
  • \(i\in S_{00},j\in S_{10} \rightarrow a_i⊕a_j=1,b_i⊕b_j=0\)\(min\)选择\(b_i⊕b_j\),计算这些\((i,j)\)的贡献,停止分治。
  • \(i\in S_{00},j\in S_{11} \rightarrow a_i⊕a_j=b_i⊕b_j=1\),该位贡献必为1,\(min\)不做出选择,分治下一位。
  • \(i\in S_{01},j\in S_{10} \rightarrow a_i⊕a_j=b_i⊕b_j=1\),该位贡献必为1,\(min\)不做出选择,分治下一位。
  • \(i\in S_{01},j\in S_{11} \rightarrow a_i⊕a_j=1,b_i⊕b_j=0\)\(min\)选择\(b_i⊕b_j\),计算这些\((i,j)\)的贡献,停止分治。
  • \(i\in S_{10},j\in S_{11} \rightarrow a_i⊕a_j=0,b_i⊕b_j=1\)\(min\)选择\(a_i⊕a_j\),计算这些\((i,j)\)的贡献,停止分治。

从分治的角度考虑,对于\(i\in S_{00}\cup S_{11},j\in S_{01}\cup S_{10}\)现在解决,对于\(S_{00}\cup S_{11}\)\(S_{01}\cup S_{10}\)递归解决。

现在解决:给出\(S_1,S_2\),求\(\sum_{i\in S_1,j\in S_2}a_i⊕a_j\)。只要按位统计\(a_i\)\(a_j\)在该位上0和1的数量即可求解,复杂度\(O(n\log a_i)\)

计算\(c_i=a_i⊕b_i\),那么\(a_i⊕a_j\)\(b_i⊕b_j\)的首个差异位即为\(c_i\)的首个1位。将\(i\)\(c_i\)排序即可自然将\(S_{00}\cup S_{11}\)\(S_{01}\cup S_{10}\)分开。或者在分治时用归并解决也可。

时间复杂度\(O(n\log^2a_i)\)

Code

//Sum of Min of Xor
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> pII;
typedef long long lint;
const int N=25e4+10;
int n,a[N],b[N];
pII c[N];
vector<int> idc[2][2];
lint ans;
void solve_left(const vector<int> &idc_i,const vector<int> &idc_j,const int x[],int k0)
{
    for(int k=k0;k>=0;k--)
    {
        lint cnt_i[2]={0},cnt_j[2]={0};
        for(int i:idc_i) cnt_i[(x[i]>>k)&1]++;
        for(int j:idc_j) cnt_j[(x[j]>>k)&1]++;
        ans+=(cnt_i[0]*cnt_j[1]+cnt_i[1]*cnt_j[0])<<k;
    }
}
void solve(int L,int R,int k)
{  
    if(L>R||k<0) return;
    idc[0][0].clear(),idc[0][1].clear(),idc[1][0].clear(),idc[1][1].clear();
    for(int i=L;i<=R;i++)
    {
        int p=c[i].second;
        int da=(a[p]>>k)&1,db=(b[p]>>k)&1;
        idc[da][db].push_back(p);
    }
    /*
    i\j 00  01  10  11
    00  0   A   B   1
    01  -   0   1   B
    10  -   -   0   A
    11  -   -   -   0
    */
    ans+=(1LL*idc[0][0].size()*idc[1][1].size())<<k;
    ans+=(1LL*idc[0][1].size()*idc[1][0].size())<<k;
    solve_left(idc[0][0],idc[0][1],a,k);
    solve_left(idc[0][0],idc[1][0],b,k);
    solve_left(idc[0][1],idc[1][1],b,k);
    solve_left(idc[1][0],idc[1][1],a,k);
    int mid=L+idc[0][0].size()+idc[1][1].size()-1;
    solve(L,mid,k-1),solve(mid+1,R,k-1);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    for(int i=1;i<=n;i++) c[i].first=a[i]^b[i],c[i].second=i;
    sort(c+1,c+n+1);
    solve(1,n,17);
    printf("%lld\n",ans);
    return 0;
}
posted @ 2021-11-17 20:24  VisJiao  阅读(100)  评论(0编辑  收藏  举报