[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 <bits/stdc++.h>
#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;
}

vector<int>g[N],s[2],cur;
vector<vector<int> >p[2];
int son[M][2],siz[M];
int n,c,k,tot=1;

void dfs(int pos,int val,vector<vector<int> >p,vector<int>&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(vector<int>s){//建立 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<k;
        if(fg)k-=tmp,res|=1ll<<i;
        for(int j=0;j<(int)s[1].size();++j)
            cur[j]=son[cur[j]][fg^(s[1][j]>>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,[](vector<int>a,vector<int>b)
        {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;
}
posted @ 2022-05-23 09:51  _Famiglistimo  阅读(95)  评论(0编辑  收藏  举报