LINK

设答案子集为x,注意到有超过 n 2 \frac{n}{2} 2n(四舍五入)的人包括 x x x

我们从 n n n个人随机选出一个人,这个人包含 x x x的概率大于等于 n 2 \frac{n}{2} 2n

多随机几次,意味着肯定随机到了参与答案的人。

比如随机到的人编号是 i d id id,那么只考虑这个人喜欢的货币子集,设这个人喜欢 l l l种货币

于是把所有人抽象为只包含这 l l l位的二进制数,这样的子集只有不多于 2 l 2^{l} 2l

下面有两种做法

①.开桶枚举子集

开个桶,把每个人表示的数字存下来,此时数组 w [ m a s k ] w[mask] w[mask]表示二进制数为 m a s k mask mask w [ m a s k ] w[mask] w[mask]个人

接下来令 x x x 0 0 0枚举到 2 l 2^l 2l,然后枚举 x x x的子集,让 x x x的子集 m a s k mask mask, v i s [ m a s k ] + = w [ x ] vis[mask]+=w[x] vis[mask]+=w[x]

最后扫描一遍 v i s vis vis数组,就可以轻松判断是否大于等于 n 2 \frac{n}{2} 2n然后更新答案


②. S O S D P \rm SOSDP SOSDP

现在我们只需要预处理一个 f [ m a s k ] f[mask] f[mask]表示子集是 m a s k mask mask的有多少人

这其实是个非常模板的 S O S D P \rm SOSDP SOSDP问题,套用模板即可

总体复杂度 O ( k ∗ p ∗ 2 p ) O(k*p*2^{p}) O(kp2p)

其中 k k k表示随机次数

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
char a[maxn][65],res[maxn];
int zhi[maxn],pos[70],bit[1<<16],f[1<<16],ans=-1,n,m,p;
mt19937 rnd(time(0));
void update(int id)
{
	pos[0] = 0;
	for(int i=1;i<=n;i++)	zhi[i] = 0;
	for(int i=1;i<=m;i++)	if( a[id][i]=='1' )	pos[++pos[0]] = i;
	for(int i=1;i<=n;i++)
	for(int j=pos[0];j>=1;j--)
		zhi[i] = zhi[i]*2+( a[i][pos[j]]=='1' );
	int mx = ( 1<<pos[0] );
	for(int i=0;i<mx;i++)	f[i] = 0;
	for(int i=1;i<=n;i++)	f[zhi[i]]++;
	for(int i=0;i<pos[0];i++)
	for(int j=0;j<mx;j++)
		if( !(j&(1<<i)) )	f[j] += f[j|(1<<i)];
	for(int i=0;i<mx;i++)
	{
		if( f[i]<(int)(n/2.0+0.5) || ans>=bit[i] )	continue;
		ans = bit[i];
		for(int j=1;j<=m;j++)	res[j] = '0';
		for(int j=0;j<pos[0];j++)
			if( (i>>j)&1 )	res[pos[j+1]] = '1';
	}
}
int main()
{
	for(int i=1;i<(1<<16);i++)	bit[i] = bit[i>>1]+(i&1);
	cin >> n >> m >> p;
	for(int i=1;i<=n;i++)	scanf("%s",a[i]+1 );	
	for(int i=1;i<=10;i++)
	{
		int id = rnd()%n+1;
		update( id );
	}
	for(int i=1;i<=m;i++)	cout << res[i];
}