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=1∑nj=1∑nk=1∑n(xi&xj)×(xj∣xk)
答案取模 1 e 9 + 7 1e9+7 1e9+7
其中 & \& & 是按位与运算 , ∣ | ∣ 是 按位或运算。
数据范围
0 ≤ x i ≤ 2 60 0\le x_i\le 2^{60} 0≤xi≤260
1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times10^5 1≤n≤5×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=1∑nj=1∑nk=1∑n(xi&xj)×(xj∣xk)=j=1∑ni=1∑nk=1∑n(xi&xj)×(xj∣xk)=j=1∑n[i=1∑n(xj&xi)]×[k=1∑n(xj∣xk)]=j=1∑nF(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=0∞2c×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=0∑∞2c×j=1∑n1−(1−wc(xi))(1−wc(xj))=c=0∑∞2c×[n−(1−wc(xi))j=1∑n(1−wc(xj))]=c=0∑∞2c×[n−(1−wc(xi))(n−cntc)]
- 式子再化简开来就没有必要了,因为 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=0∞2c×n∑c=0∞2c×cntcwc(xi)=1wc(xi)=0
个人总结
一个多值变量 x x x 如果能取值到 { v 1 , ⋯ , v n } \{v_1,\cdots,v_n\} { v1,⋯,vn} 的时候的一些表达式计算:
- 只有当 x x x 取值 v i v_i vi时,对答案贡献 k k k,否则贡献为 0 0 0 的表达式:
a n s = χ [ ( x − v 1 ) ( x − v 2 ) ⋯ ( x − v k − 1 ) ( x − v k + 1 ) ⋯ ( x − v n ) ≠ 0 ] × k ans=\chi \Big[(x-v_1)(x-v_2)\cdots(x-v_{k-1})(x-v_{k+1})\cdots(x-v_n)\ne0\Big]\times k ans=χ[(x−v1)(x−v2)⋯(x−vk−1)(x−vk+1)⋯(x−vn)=0]×k - (1.的拓展)只有当 x x x 取值为集合 S = { v i } S=\{v_i\} S={
vi}中的集合对答案贡献 k k k,否则无贡献的表达式:
a n s = k × χ [ ∏ i ( x − v i ) ≠ 0 ] ans=k\times\chi \Big[\prod_i(x-v_i)\ne0\Big] ans=k×χ[∏i(x−vi)=0],其中 v i ∉ { S } v_i\notin\{S\} vi∈/{ S} - ( n n n个多值变量拓展)只有当 x 1 ≠ v 1 , ⋯ x j ≠ v j x_1\ne v_1,\cdots x_j\ne v_j x1=v1,⋯xj=vj 同时满足时对答案贡献 k k k,否则无贡献的表达式:
a n s = k × χ [ ∏ i ( x i − v i ) ≠ 0 ] ans=k\times\chi \Big[\prod_i(x_i-v_i)\ne0\Big] ans=k×χ[∏i(xi−vi)=0]
其中 χ [ F ] \chi\Big[F\Big] χ[F] 为艾弗森括号,表示若 F F F为真则值为 1 1 1,否则值为 0 0 0。
n n n 个双值变量 x i x_i xi 只取 { 0 , 1 } \{0,1\} { 0,1} 的时候的一些表达式计算:
- 全取 1 1 1 才有贡献 k k k
a n s = k × ∏ i x i ans=k\times\prod_ix_i ans=k×∏ixi - 全取 0 0 0 才有贡献 k k k
a n s = k × ∏ i ( 1 − x i ) ans=k\times \prod_i(1-x_i) ans=k×∏i(1−xi) - 至少有一个变量取 0 0 0 就对答案有贡献 k k k (或者要么 x 1 x_1 x1取 0 ⋯ 0\cdots 0⋯要么 x n x_n xn取 0 0 0就有贡献 k k k)
a n s = k × ( 1 − ∏ i x i ) ans=k\times(1-\prod_i x_i) ans=k×(1−∏ixi) - 至少有一个变量取 1 1 1 就对答案有贡献 k k k (或者要么 x 1 x_1 x1取 1 ⋯ 1\cdots 1⋯要么 x n x_n xn取 1 1 1就有贡献 k k k)
a n s = k × ( 1 − ∏ i ( 1 − x i ) ) ans=k\times(1-\prod_i (1-x_i)) ans=k×(1−∏i(1−xi))
其他的一些细节
- 只要预处理好 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;
}