【訓練題25:數學+位運算】E : Apollo versus Pan | CF Good Bye 2020

溢流眼淚發表於2020-12-31

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<<j) 而不是 (1<<j) 否則會上溢位
    (儘管 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;
}

相關文章