[ABC252EX] K-th Beautiful Necklace 题解
Meet in the middle+Trie
Statement
给定 \(n\) 个珠子,每一个有颜色 \(d_i\) 和权值 \(v_i\) ,颜色有且仅有 \(C\) 种,并且保证每一个颜色至少出现一次
每次可以选出 \(C\) 个不同颜色的珠子,定义权值是所选珠子的异或和
求第 \(K\) 大权值
\(C\le n\le 70,v_i\le 2^{60},K\le 10^{18}\)
Solution
我们从出题人角度来考虑问题,怎样设计才能使得权值的数量最多
设 \(n_c\) 表示颜色 \(c\) 的珠子的数量,容易发现方案数即使 \(\prod n_c\),其中 \(\sum n_c=n\)
下面我们证明这个最大值应该是 \(O(3n^{\frac n3})\)
首先证明 \(n_c\neq 1\) ,这个显然不如加在其他颜色上
然后证明 \(n_c=2\) 的数量不超过 \(2\) ,因为 \(2^3<3^2\)
然后证明 \(n_c\le 3\) ,因为 \(x^2/4\ge x,x\ge 4\)
所以最大值是 \(3\times n^{\frac n3}\) 级别,由于这里 \(n=70=2\times 2+3\times 22\) ,所以最多也就是 \(2^2+3^{22}\) 种情况
可以想到如若把复杂度砍一个平方就好了,砍平方可以想到 Meet in the middle
考虑把 \(n_c\) 尽量分成 \(\prod\) 接近的两部分,然后考虑直接暴力 \(O(3^{11})\) 求出两个部分对应的可能取值集合 \(A,B\)
那么现在的问题就转化成了求 \(A,B\) 中分别选择一个数 \(a,b\),权值为 \(a\oplus b\) ,求第 \(k\) 大权值
我们首先可以得到一个二分答案的做法,即二分 \(mid\) 是不是第 \(k\) 大,即计算一下 \(mid\) 的排名
计算排名也就是计算有多少种从 \(A,B\) 中分别选择一个数的方案可以使得权值 \(\le mid\)
这个可以考虑把 A 插入 Trie ,然后遍历 B 中每一个数在 Trie 树上跑做到
具体的,对于当前的一个点,如若往左走会 \(\le x\) ,那么直接加上左子树大小然后往右边走即可
那么这样的话复杂度就是 \(O(3^{11}\log^2V)\) ,多了一个 log 过不了
考虑优化这个复杂度,我从反方向思考这个问题,本来我是二分一个权值然后求排名判断,然后我直接一手釜底抽薪,考虑能不能直接根据排名构造出解
发现是可以的,我们可以直接让 B 中所有数一起在 Trie 树上跑,因为方案数和排名是对应的,那么就可以判断往那边走了
从另外一个角度来得到这个解法,考虑到每次一个一个跑的话显然会重复的跑一些节点,那么干脆一起跑,然后可以发现一起跑就不需要二分那个权值作为辅助了
所以最后时间复杂度 \(O(3^{11}\log V)\) ,具体细节见代码
Code
#include
#define int long long
using namespace std;
const int N = 75;
const int M = 3e7+5;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1; char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
vectorg[N],s[2],cur;
vector >p[2];
int son[M][2],siz[M];
int n,c,k,tot=1;
void dfs(int pos,int val,vector >p,vector&s){//暴力求集合 AB
if(pos==p.size())return s.push_back(val),void();
for(auto v:p[pos])dfs(pos+1,val^v,p,s);
}
void build(vectors){//建立 Trie 树,官方题解好像有一种不需要实际把 Trie 树建立出来的实现方式
for(auto v:s){
int p=1;
for(int i=60;~i;--i){
int now=v>>i&1;
if(!son[p][now])
son[p][now]=++tot;
p=son[p][now],siz[p]++;
}
}
}
void solve(){
int res=0; k=s[0].size()*s[1].size()-k+1;//注意这里实际上统计的是有多少个数比他小
for(int i=60;~i;--i){
int tmp=0;
for(int j=0;j<(int)s[1].size();++j)
tmp+=siz[son[cur[j]][s[1][j]>>i&1]];
int fg=tmp>i&1)];
}
printf("%lld\n",res);
}
signed main(){
n=read(),c=read(),k=read();
for(int i=1,a,b;i<=n;++i)
a=read(),b=read(),g[a].push_back(b);
sort(g+1,g+1+c,[](vectora,vectorb)
{return a.size()>b.size();});
for(int i=1;g[i].size();++i)
p[i&1].push_back(g[i]);
dfs(0,0,p[0],s[0]),dfs(0,0,p[1],s[1]);
build(s[0]),cur.resize(s[1].size());
for(auto &v:cur)v=1;//所有都从 1 号节点出发
solve();
return 0;
}