首页 > 其他分享 >CF241B Friends 题解

CF241B Friends 题解

时间:2024-08-24 22:47:44浏览次数:9  
标签:val int 题解 ll tr dep tg CF241B Friends

异或粽子 的超级加强版,但是本题因为 \(m\) 很大,不能套用那一题的解法。


换一种思路:考虑把 \(a_i\) 从高位到低位插入 0-1 Trie 之后,二分第 \(m\) 大,通过第 \(m\) 大求答案。

对于二分的一个值 \(x\),枚举每个位置 \(i\),在 0-1 Trie 上找与 \(a_i\) 异或值大于等于 \(x\) 的值的个数。

类比求最大异或和的过程,考虑搜索到第 \(j\) 位。如果 \(x\) 的第 \(j\) 位为 \(1\),为了最终异或值大于等于 \(x\),可能的数一定在与 \(a_i\) 的第 \(j\) 位相异的子树中,递归即可;反之,如果 \(x\) 的第 \(j\) 位为 \(0\),与 \(a_i\) 的第 \(j\) 位相异的子树中的值一定全部满足条件,递归与 \(a_i\) 的第 \(j\) 位相同的子树即可。

于是可以在 \(O(n \log^2 w)\) 的时间内找到第 \(m\) 大的异或值(\(w\) 为值域,后文同),设这个值为 \(k\)。


下一步是求前 \(m\) 大两两异或值的和。

容易想到,类似前文所述,只需要处理被计算的完整的子树与 \(a_i\) 的异或值的和。(即搜索时找到的 \(k\) 的第 \(j\) 位为 \(0\),与 \(a_i\) 的第 \(j\) 位相异的子树)直接对这些子树的根节点打标记,整体遍历一次 0-1 Trie 时容易得到这棵子树内每一位上 \(0\) 和 \(1\) 的数量,答案也就容易统计了。

至多有 \(n \log w\) 个标记,处理每个标记需要枚举 \(\log w\) 位。同时,至多合并 \(O(n \log w)\) 次,单次合并的时间为 \(O(\log w)\)。综上,时间复杂度 \(O(n \log^2 w)\)。


将两部分拼起来就得到了最终做法,时间复杂度 \(O(n \log^2 w)\),可以通过。

#include <iostream>
#include <map>
#include <vector>

using namespace std;

typedef long long ll;

const ll mod = 1e9 + 7;

static inline ll qpow(ll a, ll b) {
    ll ret = 1;
    while (b) {
        if (b & 1)
            ret = ret * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ret;
}
const ll _2 = qpow(2, mod - 2);

int n, m;
int a[50005];

int rt;
map<int, vector<int>> mp;
struct trie {
    int son[2];
    int val, cnt[32];
} tr[1600005];
int cnt;
static inline int insert(int x, int dep, int p) {
    if (!p)
        p = ++cnt;
    if (dep == -1) {
        ++tr[p].val;
        return p;
    }
    int tag = (x >> dep) & 1;
    tr[p].son[tag] = insert(x, dep - 1, tr[p].son[tag]);
    tr[p].val = tr[tr[p].son[0]].val + tr[tr[p].son[1]].val;
    return p;
}
static inline ll query(int x, int aim, int dep, int p) {
    if (!p)
        return 0;
    if (dep == -1)
        return tr[p].val;
    int tg = (x >> dep) & 1;
    int tg2 = (aim >> dep) & 1;
    if (tg2)
        return query(x, aim, dep - 1, tr[p].son[tg ^ 1]);
    return tr[tr[p].son[tg ^ 1]].val + query(x, aim, dep - 1, tr[p].son[tg]);
}

ll ans, sum;
static inline void query2(int x, int aim, int dep, int p) {
    if (!p)
        return;
    if (dep == -1)
        return;
    int tg = (x >> dep) & 1;
    int tg2 = (aim >> dep) & 1;
    if (tg2)
        return query2(x, aim, dep - 1, tr[p].son[tg ^ 1]);
    if (tr[p].son[tg ^ 1])
        mp[tr[p].son[tg ^ 1]].push_back(x);
    query2(x, aim, dep - 1, tr[p].son[tg]);
}
static inline void dfs(int dep, int p, int from, int val) {
    if (!p)
        return;
    if (dep == -1) {
        if (from)
            tr[p].cnt[dep + 1] += tr[p].val;
        if (mp.count(p))
            for (auto x : mp[p]) {
                sum += tr[p].val;
                ll tot = 0;
                for (int i = 0; i <= dep + 1; ++i) {
                    int tg = (x >> i) & 1;
                    if (tg)
                        tot += (ll)(tr[p].val - tr[p].cnt[i]) * (1ll << i) % mod;
                    else
                        tot += (ll)tr[p].cnt[i] * (1ll << i) % mod;
                }
                for (int i = dep + 2; i <= 30; ++i) {
                    int tg = (x >> i) & 1;
                    int tg2 = (val >> i) & 1;
                    if (tg ^ tg2)
                        tot += (ll)tr[p].val * (1ll << i) % mod;
                }
                ans = (ans + tot) % mod;
            }
        return;
    }
    if (tr[p].son[0]) {
        dfs(dep - 1, tr[p].son[0], 0, val);
        for (int i = 0; i <= 30; ++i)
            tr[p].cnt[i] += tr[tr[p].son[0]].cnt[i];
    }
    if (tr[p].son[1]) {
        dfs(dep - 1, tr[p].son[1], 1, val | (1 << dep));
        for (int i = 0; i <= 30; ++i)
            tr[p].cnt[i] += tr[tr[p].son[1]].cnt[i];
    }
    if (from)
        tr[p].cnt[dep + 1] += tr[p].val;
    if (mp.count(p))
        for (auto x : mp[p]) {
            sum += tr[p].val;
            ll tot = 0;
            for (int i = 0; i <= dep + 1; ++i) {
                int tg = (x >> i) & 1;
                if (tg)
                    tot += (ll)(tr[p].val - tr[p].cnt[i]) * (1ll << i) % mod;
                else
                    tot += (ll)tr[p].cnt[i] * (1ll << i) % mod;
            }
            for (int i = dep + 2; i <= 30; ++i) {
                int tg = (x >> i) & 1;
                int tg2 = (val >> i) & 1;
                if (tg ^ tg2)
                    tot += (ll)tr[p].val * (1ll << i) % mod;
            }
            ans = (ans + tot) % mod;
        }
}

static inline bool check(int x) {
    ll tot = 0;
    for (int i = 1; i <= n; ++i)
        tot += query(a[i], x, 30, rt);
    tot >>= 1;
    return tot >= m;
}

static inline void solve() {
    cin >> n >> m;
    if (m == 0) {
        cout << 0 << endl;
        return;
    }
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        rt = insert(a[i], 30, rt);
    }
    int l = 1;
    int r = 1ll << 30;
    int ret = -1;
    while (l <= r) {
        int mid = (int)((l + r) >> 1);
        if (check(mid)) {
            ret = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    for (int i = 1; i <= n; ++i)
        query2(a[i], ret, 30, rt);
    dfs(30, rt, 0, 0);
    sum >>= 1;
    ans = ans * _2 % mod;
    ans = (ans + (m - sum) * ret % mod) % mod;
    cout << ans << endl;
}

signed main() {
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}

标签:val,int,题解,ll,tr,dep,tg,CF241B,Friends
From: https://www.cnblogs.com/bluewindde/p/18378414

相关文章

  • qoj8546题解补充
    题解中第二种解法并没有具体解释是如何归纳的(害笔者想了两天两夜),这里给一个证明。考虑答案为(n,n)时,只需要全取max即可,接下来我们从n往n-1归纳,接下来所有位置初始都是取max的情况1:a中的n和b中的n在同一个位置上,我们只需在这个位置上取min即可归纳到n-1,那么接下来我们钦定不会......
  • P7515 [省选联考 2021 A 卷] 矩阵游戏 题解
    DescriptionAlice有一个\(n\timesm\)的矩阵\(a_{i,j}\)(\(1\lei\len\),\(1\lej\lem\)),其每个元素为大小不超过\({10}^6\)的非负整数。Bob根据该矩阵生成了一个\((n-1)\times(m-1)\)的矩阵\(b_{i,j}\)(\(1\lei\len-1\),\(1\lej\lem-1\)),每个......
  • VS2022 Visual Studio Installer 一直卡在0%,或者下载速度慢的问题解决办法
    C:\Users\Administrator\AppData\Local\Temp到c盘查看日志,发现是下载一个叫vs_installer.opc的东西失败了, 直接复制日志里的https://aka.ms/vs/17/release/installer,下载,发现成功下载,然后放到installer安装器同级目录,重新打开setup安装,就成功了打开了,然后会一直正在准备中,......
  • P10933 创世纪 题解
    题目传送门前置知识树形DP解法将\(a_{i}\)向\(i\)连一条有向边,这样就形成了基环外向树森林。设\(f_{x,0/1}\)表示\(x\)不选/选时,以\(x\)为根的子树的最多选择个数,状态转移方程为\(\begin{cases}f_{x,0}=\sum\limits_{y\inSon(x)}\max(f_{y,0},f_{y,1})\\f_......
  • P10957 环路运输 题解
    题目传送门前置知识单调队列/单调栈优化解法在仓库\(1\)和\(n\)之间把环断开,然后复制一倍接在末尾,形成长度为\(2n\)的直线公路,即有\(a_{i}=a_{i+n}(1\lei\len)\)。对于原来环形公路上的任意两座仓库\(i,j(1\lej<i\len)\),代价为\(\begin{cases}a_{i}+a_{j}......
  • Chain Contestant 题解
    前言题目链接:洛谷;AtCoder。最慢的点才跑\(2\)ms的题解确定不看一看?题意简述给定长度为\(n\)的字符串\(s\),其中\(s_i\in\Omega\),求有多少子序列\(T\)满足任意\(x\in\Omega\),其在\(T\)出现的位置为连续一段,当然,对\(998244353\)取模。\(n\leq10^5\),\(|\Omeg......
  • P3547 [POI2013] CEN-Price List 题解
    Description给定一个\(n\)个点\(m\)条边的无向图,边权均为\(a\)。现在将原来图中满足最短路等于\(2a\)所有的点对\((x,y)\)之间加一条长度为\(b\)的无向边。给定\(k\),求点\(k\)到所有点的最短路是多少。\(1\leqn,m\leq10^5\)。Solution首先有个显然的结论是......
  • 洛谷P2440 木材加工 题解
    这是我找到的一篇很久之前为了让我更好理解二分写的详细题解题目链接code:#include<bits/stdc++.h>#defineintlonglong#defineMAXN0x3f3f3f3f3f3f3f3f#defineMINN-0x3f3f3f3f3f3f3f3f#defineMiraiios::sync_with_stdio(0),cin.tie(0),cout.tie(0);usingnamespa......
  • CF939F Cutlet题解
    前置单调队列(没学过或忘了点这里)简化题意有一块牛排,要求对它烹饪\(2n\)秒,可在给定的\(k\)个时间段中将它翻转任意次,使得牛排两面都受到了\(n\)秒的烹饪。状态设计可以发现当总共煮了\(i\)秒,其中一面如果煮了\(j\)秒,自然可以求出另一面为\(i-j\)秒,所以我们可以......
  • CSP 2023 提高级第一轮 CSP-S 2023初试题 程序阅读第三题解析
    一、程序阅读#include<vector>#include<algorithm>#include<iostream>usingnamespacestd;boolf0(vector<int>&a,intm,intk){ints=0;for(inti=0,j=0;i<a.size();i++){while(a[i]-a[j]>......