title
魔法之龙玛里苟斯最近在为加基森拍卖师的削弱而感到伤心,于是他想了一道数学题。
S 是一个可重集合,S={a1,a2,…,an}。
等概率随机取 S 的一个子集 A={ai1,…,aim}。
计算出 A 中所有元素异或和,记为 x, 求 x^k 的期望。
Input
第一行两个正整数 n, k。
以下 n 行每行一个整数,表示 ai。
Output
如果结果是整数,直接输出。如果结果是小数(显然这个小数是有限的),输出精确值(末尾不加多余的 0)。
Sample Input
4 2 0 1 2 3
Sample Output
3.5
Hint
限制与约定
1≤n≤100000,1≤k≤5,ai≥0。最终答案小于 2^63 。k=1,2,3,4,5 各自占用 20% 的数据
solution
期望=概率*答案值(我一直是这么计算期望这一类题目的)
直接走正解,废话不多说,因为我也不会引入
考虑
的情况
我们把答案值
分解成二进制
如果第
位上为
,那么这个
就会对答案造成
,即
的贡献
那么我们怎么如何确定第
位是不是
呢?
很简单,看
数组里面有多少个数二进制第
位为
假设有
个,可以知道在这
个里面选择的个数的奇偶性会影响最后
位是否为
选择了奇数个,异或后就是
,否则就是
而剩下的
个数不管选不选,都不会对
这一位造成影响,因为这一位上面它们都是
那么在 里面选择个数的奇偶性的概率又分别是多少呢?这里给出一个结论
奇偶性的概率一样,都是
我尽力感性证明一下
举个栗子
,二进制分别为
就只看从右往左数的第二位(
),都是
对吧。【注意从左往右第一位开始分别是
】
接着分类讨论
如果只选
个,有
种情况
如果只选
个,有
种情况
如果只选
个,有
种情况
如果只选
个,有
种情况
选偶数个的情况总数
选
个
选
个:
选奇数个的情况总数
选
个
选
个:
惊奇的一样!!
别急,再煮个栗子
,二进制分别是
,这一次就只看从左往右数的第三位(
)
如果只选
个,有
种情况
如果只选
个,有
种情况
如果只选
个,有
种情况
如果只选
个,有
种情况
如果只选
个,有
种情况
选偶数个的情况总数
选
个
选
个
选
个:
选奇数个的情况总数
选
个
选
个:
惊奇的又一样!!
而且仔细看这堆数字可以自然地联想到杨辉三角!!(其实是因为上面的情况可以看成组合数)
如果还是觉得很巧,不太有说服性,或者没想通的,我们再另辟蹊径
把这种奇偶选择操作看成按灯泡开关,上图!
已经尽力了总结一下,只要有数能负责
位,就会有一半的概率产生
的贡献
的情况,最高位肯定小于
,
设
表示当选取情况为
的时候,
这一位是
,贡献就是
只有
才会产生上面的贡献,即为
接着我们来分类讨论,注意i,j表示二进制下的第i位,第j位为1/0
①:
意思就是
至少有一个是没有任何数可以负责的,就是没有任何一个数的二进制在那一位上面为
贡献自然是
②:
结合
的情况下,我们知道选完数后要保证
这一位上的值为
才能对答案产生影响嘛
这个概率是
,
同理,那么要让
同时为
,概率就是
但,BUT,However,unfortunately,unluckily
情况不止步于此,我们忽略掉了可能存在一些数二进制
位都为
这样的话如果选择它,就会同步影响
,所以我们要重新再分类讨论
结合
按灯泡开关的思想,直接上图
其实
的情况是最简单的,氧化钙!!
因为答案保证
,考虑第
位会产生贡献,那么它对答案的影响就是
,除掉
可以得到
,比
位更大的位置上一定都是
很容易想嘛,假设
这位有
就会产生
,最小的
也会炸掉
答案范围
所以我们就直接暴力枚举每一个数选与不选,生成子集
,然后去算期望
注意只考虑线性基里面的每一个数,因为线性基可以异或出原数组的每一个数,自然也可以异或出原数组的异或和
记线性基的个数为
一共有
种情况,每一种情况都是
用
将期望拆开算,不然会炸
code
#include <cstdio>
#define int unsigned long long
#define MAXN 100005
int n, k, ans, cnt, r;
int a[MAXN], f[65], s[65];
bool vis[65];
int read() {
int x = 0; char s = getchar();
while( s < '0' || s > '9' ) s = getchar();
while( '0' <= s && s <= '9' )
x = ( x << 1 ) + ( x << 3 ) + ( s - '0' ), s = getchar();
return x;
}
void solve1() {
for( int i = 1;i <= n;i ++ ) ans |= a[i];
if( ans & 1 ) printf( "%llu.5", ans >> 1 );
else printf( "%llu", ans >> 1 );
}
bool check( int i, int j ) {
if( ! vis[i] || ! vis[j] ) return 0;
for( int t = 1;t <= n;t ++ ) {
if( ( a[t] & ( 1ll << i ) ) && ! ( a[t] & ( 1ll << j ) ) )
return 0;
if( ( a[t] & ( 1ll << j ) ) && ! ( a[t] & ( 1ll << i ) ) )
return 0;
}
return 1;
}
void solve2() {
for( int i = 0;i < 33;i ++ )
for( int j = 1;j <= n;j ++ )
if( a[j] & ( 1ll << i ) ) { vis[i] = 1; break; }
for( int i = 0;i < 33;i ++ )
for(int j = i;j < 33;j ++ )
if( vis[i] && vis[j] ) ans += ( 1ll << ( i + j ) );
for( int i = 0;i < 33;i ++ )
for( int j = i + 1;j < 33;j ++ )
if( check( i, j ) ) ans += ( 1ll << ( i + j ) );
if( ans & 1 ) printf( "%llu.5", ans >> 1 );
else printf( "%llu", ans >> 1 );
}
void calc( int val ) {
int mod = 1ll << cnt;
int D = 0, R = 1;
for( int i = 1;i <= k;i ++ )
D = D * val, R = R * val, D += R / mod, R %= mod;
ans += D, r += R, ans += r / mod, r %= mod;
}
void dfs( int x, int val ) {
if( x > cnt ) { calc( val ); return; }
dfs( x + 1, val ); dfs( x + 1, val ^ s[x] );
}
void solve3() {
for( int i = 1;i <= n;i ++ ) {
int val = a[i];
for( int j = 21;~ j;j -- )
if( ( 1ll << j ) & val )
if( f[j] ) val ^= f[j];
else { f[j] = val; break; }
}
for( int i = 0;i <= 21;i ++ )
if( f[i] ) s[++ cnt] = f[i];
dfs( 1, 0 );
if( r ) printf( "%llu.5", ans );
else printf( "%llu", ans );
}
signed main() {
n = read(); k = read();
for( int i = 1;i <= n;i ++ )
a[i] = read();
if( k == 1 ) solve1();
else if( k == 2 ) solve2();
else solve3();
return 0;
}