【训练题25:数学+位运算】E : Apollo versus Pan | CF Good Bye 2020

E : Apollo versus Pan | CF Good Bye 2020

题外话

有可能是太晚了太困了,或者是按位运算的数学太差了///
做了好久都没有起色,于是草草睡觉了
第二天补一下题

难度

− 3370 14603 -\frac{3370}{14603} 146033370

题意

给你一个长度为 n n n 的序列 x n x_n xn
让你求
∑ i = 1 n ∑ j = 1 n ∑ k = 1 n ( x i & x j ) × ( x j ∣ x k ) \sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n(x_i\&x_j)\times(x_j|x_k) i=1nj=1nk=1n(xi&xj)×(xjxk)
答案取模 1 e 9 + 7 1e9+7 1e9+7
其中 & \& &按位与运算 , ∣ | 按位或运算

数据范围

0 ≤ x i ≤ 2 60 0\le x_i\le 2^{60} 0xi260
1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times10^5 1n5×105

思路

  • 这种按位运算的题,大概率都是按位处理的。 (大概)

  • 但是先要处理一下式子,不然只能 O ( N 3 ) O(N^3) O(N3) 硬算不实际。

  • 原 式 = ∑ i = 1 n ∑ j = 1 n ∑ k = 1 n ( x i & x j ) × ( x j ∣ x k ) = ∑ j = 1 n ∑ i = 1 n ∑ k = 1 n ( x i & x j ) × ( x j ∣ x k ) = ∑ j = 1 n [ ∑ i = 1 n ( x j & x i ) ] × [ ∑ k = 1 n ( x j ∣ x k ) ] = ∑ j = 1 n F ( j ) × G ( j ) \begin{aligned}原式&=\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n(x_i\&x_j)\times(x_j|x_k)\\ &=\sum_{j=1}^n\sum_{i=1}^n\sum_{k=1}^n(x_i\&x_j)\times(x_j|x_k)\\ &=\sum_{j=1}^n\Bigg[\sum_{i=1}^n(x_j\&x_i)\Bigg]\times\Bigg[\sum_{k=1}^n(x_j|x_k)\Bigg]\\ &=\sum_{j=1}^n F(j)\times G(j) \end{aligned} =i=1nj=1nk=1n(xi&xj)×(xjxk)=j=1ni=1nk=1n(xi&xj)×(xjxk)=j=1n[i=1n(xj&xi)]×[k=1n(xjxk)]=j=1nF(j)×G(j)

  • 其实这题的难点就是怎么求这两个 F ( i ) 、 G ( i ) F(i)、G(i) F(i)G(i) 了。

F ( i ) F(i) F(i)

  • 容易想到,应该按位考虑
  • 对于二进制右数第 c c c位(base为0),只有两个数的这一位都为 1 1 1,才会对答案贡献 2 c 2^c 2c
  • 那我们计算 F ( i ) F(i) F(i) , 首先要求 x i x_i xi 的这一位必须是 1 1 1,接下来其他数有多少个数这一位是 1 1 1,就对答案贡献几次
  • c n t c cnt_c cntc 表示有多少个数字二进制右数第 c c c位为 1 1 1
  • w c ( x i ) w_c(x_i) wc(xi)表示数字 x i x_i xi的二进制右数第 c c c位的值(为 0 0 0 或为 1 1 1
  • F ( i ) = ∑ c = 0 ∞ 2 c × w c ( x i ) × c n t c F(i)=\sum_{c=0}^{\infin} 2^c\times w_c(x_i)\times cnt_c F(i)=c=02c×wc(xi)×cntc ,答案=每次贡献价值*贡献次数。

G ( i ) G(i) G(i)

  • 这个玩意儿第一次碰到,害,也不会推,但是现在看懂了就推的套路了
  • 对于第 c c c 位,如果 w c ( x i ) = 1 w_c(x_i)=1 wc(xi)=1已经成立了,那么另外的数字选择什么,对于答案的贡献都是 2 c 2^c 2c
  • 如果 w c ( x i ) = 0 w_c(x_i)=0 wc(xi)=0,那么另外的数字需要选择 w c ( x j ) = 1 w_c(x_j)=1 wc(xj)=1 的数字,才会对答案贡献 2 c 2^c 2c
    写成数学的语言,就是两个逻辑关系的并,写成表达式为:
  • G ( i ) = ∑ c = 0 ∞ 2 c × ∑ j = 1 n 1 − ( 1 − w c ( x i ) ) ( 1 − w c ( x j ) ) = ∑ c = 0 ∞ 2 c × [ n − ( 1 − w c ( x i ) ) ∑ j = 1 n ( 1 − w c ( x j ) ) ] = ∑ c = 0 ∞ 2 c × [ n − ( 1 − w c ( x i ) ) ( n − c n t c ) ] \begin{aligned} G(i)&=\sum_{c=0}^{\infin}2^c\times\sum_{j=1}^n 1-(1-w_c(x_i))(1-w_c(x_j))\\ &=\sum_{c=0}^{\infin}2^c\times \Bigg[ n-(1-w_c(x_i))\sum_{j=1}^n(1-w_c(x_j))\Bigg]\\ &=\sum_{c=0}^{\infin}2^c\times \Bigg[ n-(1-w_c(x_i))(n-cnt_c)\Bigg] \end{aligned} G(i)=c=02c×j=1n1(1wc(xi))(1wc(xj))=c=02c×[n(1wc(xi))j=1n(1wc(xj))]=c=02c×[n(1wc(xi))(ncntc)]
  • 式子再化简开来就没有必要了,因为 w c ( x i ) w_c(x_i) wc(xi)取值 1 1 1 或者 0 0 0 算式都很简单。
  • G ( i ) = { ∑ c = 0 ∞ 2 c × n w c ( x i ) = 1 ∑ c = 0 ∞ 2 c × c n t c w c ( x i ) = 0 G(i)=\begin{cases} \sum_{c=0}^{\infin}2^c\times n &&w_c(x_i)=1\\ \sum_{c=0}^{\infin}2^c\times cnt_c &&w_c(x_i)=0\\ \end{cases} G(i)={ c=02c×nc=02c×cntcwc(xi)=1wc(xi)=0

其他的一些细节

  • 只要预处理好 c n t i cnt_i cnti 就可以了,其他内容都可以很简单计算得到。
  • 因为 x i x_i xi最多取值到 2 60 2^{60} 260,因此式子中的 c ∈ { 0 , 1 , ⋯   , 60 } c\in\{0,1,\cdots,60\} c{ 0,1,,60},复杂度并不高
  • 取第 c c c位的代码应该写成(1LL< 而不是 (1< 否则会上溢出
    (尽管 j j j 已经是long long类型了)

核心代码

时间复杂度 O ( 61 × N ) O(61\times N) O(61×N)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 5e5+50;
const ll  MOD = 1e9+7;

ll aa[MAX];
ll cnt[100];

int main()
{
     
    int T;cin >> T;
    while(T--){
     
        int n;scanf("%d",&n);
        memset(cnt,0,sizeof(cnt));
        for(int i = 1;i <= n;++i){
     
            scanf("%lld",&aa[i]);
            for(ll j = 0;j <= 60;++j){
     
                if((1LL << j) & aa[i])cnt[j]++;
            }
        }
        ll ans = 0;
        for(int i = 1;i <= n;++i){
     
            ll base = 1;
            ll t1 = 0,t2 = 0;
            for(ll j = 0;j <= 60;++j){
     
                if((1LL << j) & aa[i]){
     
                    t1 = (t1 + base * cnt[j] % MOD) % MOD;
                    t2 = (t2 + base * n % MOD) % MOD;
                }else{
     
                    t2 = (t2 + base * cnt[j] % MOD) % MOD;
                }
                base = base * 2 % MOD;
            }
            ans = (ans + t1 * t2 % MOD) % MOD;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

你可能感兴趣的:(【各类ACM真题】,算法)