跳转至

二进制

一些总和了若干二进制性质的题。

练习 #1 序列合并

题目描述

给定一个长度为 \(n\) 的非负整数序列 \(\{a_n\}\),你可以进行 \(k\) 次操作,每次操作你选择两个相邻的数,把它们合并成它们的按位或。

形式化地,一次操作中,你选择一个下标 \(i\)\(1 \le i < n\)),然后把原序列变成 \(\{a_1,a_2,\cdots,a_i \operatorname{or} a_{i+1},a_{i+2},\cdots,a_n\}\)

\(k\) 次操作后所有数按位与的最大值。

输入格式

第一行包含两个正整数 \(n,k\)

第二行包含 \(n\) 个非负整数,其中第 \(i\) 个非负整数为 \(a_i\)

输出格式

输出一行,包含一个正整数,代表答案。

对于所有数据,保证 \(1 \le k<n \le 2 \times 10^5\)\(0 \le a_i < 2^{30}\)


/*  Erica N  */
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first
#define itn int

#define rd read()
int read(){
    int xx = 0, ff = 1;char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')ff = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9')xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}

#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }


const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;
int a[N];

/*
贪心

- 从高位开始考虑,如果要将ans这一位为1,这需要将0都或上1
- 一旦有一位可以操作使ans这一位=1,那么必定操作

我们不要具体深入考虑合并应该向左还是向右,我们直接考虑:当前可以合并除的答案为r,那么r+2^i能否合并出?
判断是否可以合并出r,,即贪心地将序列划分为多的块,使得买一块的或值&r=r。
如果块数>n-K那么就可以。

*/

int n,K;

bool check(int x){
    int t=0;
    int cnt=0;
    for(int i=1;i<=n;i++){
        t|=(a[i]);
        if((t&x)==x){
            t=0;
            cnt++;
        }
    }
    return cnt>=n-K;
}

void solve(){
     n=rd;
     K=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd;
    }


    int ans=0;

    for(int i=30;~i;i--){
        ans+=(1<<i);
        if(!check(ans))ans-=(1<<i);
    }


    cout<<ans<<endl;



}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);

    int T=1;
    while(T--){
        solve();
    }
    return 0;
}

练习 #2 Rainbow的信号

Freda 发明了传呼机之后,rainbow 进一步改进了传呼机发送信息所使用的信号。

由于现在是数字、信息时代,rainbow 发明的信号用 \(N\) 个自然数表示。

为了避免两个人的对话被大坏蛋 VariantF 偷听,rainbow 把对话分成 \(A、B、C\) 三部分,分别用 \(a、b、c\) 三个密码加密。

现在 Freda 接到了 rainbow 的信息,她的首要工作就是解密。

Freda 了解到,这三部分的密码计算方式如下:

\(1 \sim N\)\(N\) 个数中,等概率地选取两个数 \(l、r\),如果 \(l>r\),则交换 \(l、r\)

把信号中的第 \(l\) 个数到第 \(r\) 个数取出来,构成一个数列 \(P\)

\(A\) 部分对话的密码是数列 \(P\)\(xor\) 和的数学期望值,\(xor\) 和就是数列 \(P\) 中各个数异或之后得到的数; \(xor\) 和的期望就是对于所有可能选取的 \(l、r\),所得到的数列的 \(xor\) 和的平均数。

\(B\) 部分对话的密码是数列 \(P\)\(and\) 和的期望,定义类似于 \(xor\) 和。

\(C\) 部分对话的密码是数列 \(P\)\(or\) 和的期望,定义类似于 \(xor\) 和。

请你帮忙计算这三个密码。

输入格式

第一行一个正整数 \(N\)

第二行 \(N\) 个自然数,表示 Freda 接到的信号。

输出格式

一行三个实数,分别表示 \(xor\) 和、\(and\) 和、\(or\) 和的期望,四舍五入保留 \(3\) 位小数,相邻两个实数之间用一个空格隔开。

对于 \(20 \%\) 的数据, \(1 \le N \le 100\) 。 对于 \(40 \%\) 的数据, \(1 \le N \le 1000\) 。 对于另外 \(30 \%\) 的数据, \(N\) 个数为 \(0\)\(1\) 。 对于 \(100 \%\) 的数据, \(1 \le N \le 100000\)\(N\) 个自然数均不超过 \(10^9\)


我们对每一位拆开考虑。

假设当前考虑到了 \(a_x\) 的第 \(i\) 位,我们记为 \(cur\)(值为 \(1\) 或者 \(0\))。我们只考虑将 \(x\) 作为右端点的情况。使用**期望的定义**。

对于不同的区间,其出现的概率分为以下两种:

  • \(l=r\) 的区间出现的概率为 \(\frac{1}{n^2}\)

  • \(l≠r\) 的区间出现的概率为 \(\frac{2}{n^2}\)

我们记 \(pre1\) 为上一个 \(1\) 的下标,\(pre0\) 同理。

下面我们来分类讨论:

  • xor

    xor 的情况最为复杂。 我们要记录 \(c0\)\([1,x]\) 中使得区间 \([l,x]\) 的 xor 为 \(0\)\(l\) 的个数。\(c1\) 同理。 如果 \(cur=1\),左端点可以为 \(c0\)。那么概率就是 \(c0\times \frac{2}{n^2}\),期望的话再乘上一个 cur<<i 即可。交换 \(c1,c0\)。 如果 \(cur=0\),左端点可以为 \(c1\)。那么概率就是 \(c1\times \frac{2}{n^2}\),期望的话再乘上一个 cur<<i 即可。 最后,若 \(cur=0\),则 c0++,否则 c1++

  • and

    如果 \(cur=1\),左端点可以为 \([pre0+1,x]\)。那么概率就是 $ \frac{1}{n^2}+(pre0-x-1)\times \frac{2}{n^2}$,期望的话再乘上一个 cur<<i 即可。

    如果 \(cur=0\),无贡献。

  • or

    如果 \(cur=1\),左端点可以为 \([1,x]\)。那么概率就是 $ \frac{1}{n^2}+(x-1)\times \frac{2}{n^2}$,期望的话再乘上一个 cur<<i 即可。

    如果 \(cur=0\),左端点可以为 \([1,pre1]\)。那么概率就是 \(pre1\times \frac{2}{n^2}\),期望的话再乘上一个 cur<<i 即可。

/*
                      Keyblinds Guide
                                ###################
      @Ntsc 2024

      - Ctrl+Alt+G then P : Enter luogu problem details
      - Ctrl+Alt+B : Run all cases in CPH
      - ctrl+D : choose this and dump to the next
      - ctrl+Shift+L : choose all like this
      - ctrl+K then ctrl+W: close all
      - Alt+la/ra : move mouse to pre/nxt pos'

*/
#include <bits/stdc++.h>
#include <queue>
using namespace std;

#define rep(i, l, r) for (int i = l, END##i = r; i <= END##i; ++i)
#define per(i, r, l) for (int i = r, END##i = l; i >= END##i; --i)
#define pb push_back
#define mp make_pair
#define int long long
#define pii pair<int, int>
#define ps second
#define pf first
#define ull unsigned long long

#define itn int
// #define inr int
// #define mian main
// #define iont int

#define rd read()
int read() {
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

#define ell dbg('\n')
const char el = '\n';
const bool enable_dbg = 1;
template <typename T, typename... Args>
void dbg(T s, Args... args) {
    if constexpr (enable_dbg) {
        if (1)
            cerr << ' ';
        cerr << s;
        if constexpr (sizeof...(Args))
            dbg(args...);
    }
}

#define zerol = 1
#ifdef zerol
#define cdbg(x...)            \
    do {                      \
        cerr << #x << " -> "; \
        err(x);               \
    } while (0)
void err() { cerr << endl; }
template <template <typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v : a) cerr << v << ' ';
    err(x...);
}
template <typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif

const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;

/*
我们对每一位拆开考虑:
假设当前考虑到了aq_x的第i位,我们记为cur(1/0)。我们只考虑将x作为右端点的情况。使用期望的定义
对于不同的区间,其出现的概率分为以下两种:
- l=r   1/n^2
- l!=r  2/n^2
下面我们来分类讨论:
我们记pre1为上一个1的下标,pre0同理
- xor
xor的情况最为复杂
我们要记录c0为[1,x]中使得区间[l,x]的xor为0的l的个数。c1同理。
如果cur=1,左端点可以为c0。那么概率就是c0*2/n^2,期望的话再乘上一个cur<<i即可。交换c1,c0
如果cur=0,左端点可以为c1。那么概率就是c1*2/n^2,期望的话再乘上一个cur<<i即可。
最后,若cur=0,则c0++,否则c1++
- and
如果cur=1,左端点可以为[pre0+1,x]。那么概率就是1/n^2+(pre0-x-1)*2/n^2,期望的话再乘上一个cur<<i即可。
如果cur=0,无贡献
- or
如果cur=1,左端点可以为[1,x]。那么概率就是1/n^2+(x-1)*2/n^2,期望的话再乘上一个cur<<i即可。
如果cur=0,左端点可以为[1,pre1]。那么概率就是(pre1)*2/n^2,期望的话再乘上一个cur<<i即可。


*/

itn pre[3];
int a[N];
double ansxor, ansor, ansand;

void solve() {
    int n = rd;
    for (int i = 1; i <= n; i++) {
        a[i] = rd;
    }

    for (itn i = 0; i < 31; i++) {
        pre[0] = pre[1] = 0;

        int c0 = 0, c1 = 0;

        for (int j = 1; j <= n; j++) {
            itn cur = (a[j] >> i) & 1;
            double res = 1. * (1 << i) / (n * n);
            //          cdbg(res);
            if (cur) {
                ansxor += res + 2. * res * c0;
                ansand += res + 2. * res * (j - pre[0] - 1);
                ansor += res + 2. * (res * (j - 1));
            } else {
                ansxor += 2. * res * c1;
                ansor += 2. * (res * pre[1]);
            }

            pre[cur] = j;
            c0++;
            if (cur)
                swap(c0, c1);
        }
    }

    printf("%.3lf %.3lf %.3lf\n", ansxor, ansand, ansor);
}

signed main() {
    freopen("interval.in", "r", stdin);
    freopen("interval.out", "w", stdout);

    int T = 1;
    while (T--) {
        solve();
    }
    return 0;
}