[BZOJ3811]玛里苟斯

题目

传送门 to usOJ

题目概要
对于可重集合 S S S ,设其元素个数为 n n n ,显然它有 2 n 2^n 2n 个子集。定义集合的权值为其中元素的异或和,求子集的权值的 k k k 次方的期望。即: ∑ s ⊆ S f ( s ) k 2 n \frac{\sum_{s\subseteq S}f(s)^k}{2^n} 2nsSf(s)k 的值, f ( s ) f(s) f(s) 是集合内元素的异或和。

数据范围与约定
n ≤ 1 0 5 , k ≤ 5 n\le 10^5,k\le 5 n105,k5

思路

F B L      W A R N I N G : 我 将 试 着 使 用 奇 怪 的 文 风 去 论 述 这 个 问 题 。 \bf{FBL\;\;WARNING}:我将试着使用奇怪的文风去论述这个问题。 FBLWARNING使

刚看一眼,就被这道题给吓住了, n n n 很大, k k k 却小的可怜, 5 5 5 站在 1 0 5 10^5 105 旁边,显得毫不起眼。但我们不着眼于出题人的区别对待,我们要拯救每一个 k k k ,让世界重新充满爱。

  • k = 1 k=1 k=1 的时候,这道题变得平淡无奇,味同嚼蜡。所有的 f f f 中,有一半都满足第 i i i 位是一个 1 1 1 ,只要 S S S 中存在一个数字可以提供这个 1 1 1 。这是因为,这个数字是否出现,这件事不影响别人,它只负责自己的选择,就像 z x y zxy zxy 默默的 A K AK AK 。若夫它看到是 0 0 0 ,它便义无反顾地将自己融入整个群体;至若此值已经为 1 1 1 ,它不会出现在人们的视野中,好像从未出现过,又好像一直都在。
  • k = 2 k=2 k=2 的时候,我们要做的是一个平方的选择。两个元素,虽然不在一个括号中,但是二人的心却在一起,这就是平方的真谛。二者何人?枚举则知。不是所有人都能够相遇,就像人生总会有很多遗憾。如果二者可以被独立抉择——存在一个数字,在第 i i i 位为 1 1 1 而第 j j j 位为 0 0 0 ——那么二人是不容易遇到的,只有 1 2 × 1 2 = 1 4 \frac{1}{2}\times\frac{1}{2}=\frac{1}{4} 21×21=41 。如果不是独立抉择,就会翻倍,得到 1 2 \frac{1}{2} 21 了。
  • k ≥ 3 k\ge 3 k3 的时候,我们终于可以大展拳脚了。我们有很多方法。我们可以利用答案小于 2 63 2^{63} 263 的特点,直接猜到 a ≤ 2 22 a\le 2^{22} a222 次方,求出线性基,然后暴力枚举。我们也可以继续学习 k = 2 k=2 k=2 的方案。

我改悔了。

对于 k = 2 k=2 k=2 ,我们拆位,将一个数字拆成很多个 2 2 2 的幂之和。然后平方就是选两个二进制位呗。


假设最大的一个二进制位是 2 x 2^x 2x ,那么至少有一半的异或和不小于 2 x 2^x 2x ,与 k = 1 k=1 k=1 的情况类似。

所以答案至少有 1 2 ⋅ ( 2 x ) k < 2 63 \frac{1}{2}\cdot(2^x)^k<2^{63} 21(2x)k<263 ,可以解出 x < 64 k x<\frac{64}{k} x<k64 ,然后就没了。

作乘法可能会爆 u n s i g n e d    l o n g    l o n g unsigned\;long\;long unsignedlonglong ,稍微打的骚一点就好了。

代码

// 湘妹儿,永远滴神!
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef unsigned long long int_;
int_ readint(){
	int_ x; scanf("%llu",&x);
	return x; // cin就能兼容了[滑稽]
}

int_ d[100];
void insert(int_ x){
	for(int i=63; i>=0&&x; --i)
		if(x>>i&1){
			if(d[i] == 0)
				d[i] = x;
			x ^= d[i];
		}
}

int n, k, cnt; // 分母是pow(2,cnt)
int_ zheng, xiao; // 整数与小数
void dfs(int t,int_ now){
	if(t == -1){
		int_ res = 1; // 加入res*now
		for(int i=1; i<k; ++i)
			res *= now;
		int_ yu = res%(1<<cnt);
		res = res/(1<<cnt);
		// 变成了res*(1<<cnt)+yu
		zheng += res*now;
		xiao += yu*now;
		zheng += xiao/(1<<cnt);
		xiao = xiao%(1<<cnt);
		return ;
	}
	if(d[t]) dfs(t-1,now);
	dfs(t-1,now^d[t]);
}
int main(){
	n = readint(), k = readint();
	for(int i=0; i<n; ++i)
		insert(readint());
	int_ all = 0;
	for(int i=63; i>=0; --i)
		all |= d[i];
	if(k == 1){
		cnt = 1;
		zheng = all>>1;
		xiao = all&1;
	}
	if(k == 2){
		cnt = 2;
		for(int i=32; i>=0; --i)
		if(all>>i&1)
		for(int j=32; j>=0; --j)
		if(all>>j&1){
			bool apart = false;
			for(int o=32; o>=0; --o)
				if((d[o]>>i&1)^(d[o]>>j&1))
					{ apart = true; break; }
			int mom = (apart ? 1 : 0)+1;
			if(i+j >= mom)
				zheng += (1ull<<(i+j-mom));
			else
				xiao += 1ull<<(i+j+cnt-mom);
			// 为了让分母pow(2,mom->cnt)
			zheng += xiao/(1<<cnt);
			xiao = xiao%(1<<cnt);
		}
	}
	if(k >= 3){
		for(int i=0; i<=63; ++i)
			if(d[i] > 0) ++ cnt;
		dfs(63,0);
	}
	printf("%llu",zheng);
	if(xiao) putchar('.');
	while(xiao *= 10){
		printf("%llu",xiao/(1<<cnt));
		xiao %= (1<<cnt);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值