AtCoder Regular Contest 127
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;
}