题意
有(n)个集合与和一个有(m)个元素的全集(U),每个集合都是(U)的子集,且每个集合大小不超过(p),求一个最大的(U)的子集(A),使得(A)至少是(n)个集合中(lceil frac{n}{2} ceil)个集合的子集。
(1le nle 2 imes10^5,1le ple mle 60,1le ple 15)
题解
如果我么随机从(n)个和中随机选(k)个集合,再枚举它的子集作为答案。由于(A)至少是(n)个集合中(lceil frac{n}{2} ceil)个集合的子集,这样错误的概率为(frac{1}{2^k}),错误的概率很小,所以我们随机一定次数从(n)个集合中选集合然后验证。下面考虑如何快速验证一个集合是否满足条件。
由于每个集合最多只有(p)个元素,我们只需考虑其他集合中这(p)个元素的存在情况。于是可以根据当前待验证的集合将其他集合表示为长度不超过(p)的位图,设他们分别为(a_i),不妨设当前集合为(a_1),则其一个子集(b(b&a_1=b,b|a_1=a_1))能作为答案的条件为满足(b&a_i=b)的(a_i)不少于(lceil frac{n}{2} ceil)个。于是可以用(FWT)来统计答案,令(A_k=sum_{i=2}^{n}[a_i==k]),对于(fwtand)有
[FWTAND[A]_i=sum_{j&i=i}A_j
]
故此情况下可以将答案更新为(max{ card(i) |A_igelceil frac{n}{2} ceil})。
记(T)为随机的次数,则总复杂度为(O(Tp(2^p+n)))。
(old{PS:})生成的随机数类型为unsigned int
,范围为(0sim 2^{32}-1),如果要得到的随机数为int
要先模(n)再强转为int
,否则会出现负数!
#include <bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int N=3e5+10,INF=1<<30;
int n,m,p=0,c[N],aa[N],res[62];
char s[75];
int pc[(1<<15)+10];
void fwtand(ll *a,int n,int typ){
for(int mid=1;mid<n;mid<<=1){
for(int r=(mid<<1),j=0;j<n;j+=r){
for(int i=0;i<mid;i++){
a[i+j]=(a[i+j]+typ*a[i+j+mid]);
}
}
}
}
ll ov[N];
ll a[(1<<15)+10];
unsigned sd=chrono::system_clock::now().time_since_epoch().count();
mt19937 rndnum(sd);
int rnd(int n){
unsigned res=rndnum()%n+1;
return res;
}
int vis[N];
void f1(){
for(int i=1,l=(1<<15);i<l;i++)pc[i]=pc[i>>1]+(i&1);
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++){
//aa[i]=i;
vis[i]=0;
scanf("%s",s+1);
for(int j=1;j<=m;j++){
if(s[j]=='1'){ov[i]|=(1ll<<(j-1));}
}
}
int ans=0;
for(int o=1;o<=min(200,n);o++){
int u=rnd(n);
while(vis[u]){
u=rnd(n);
//assert(u<=n&&u>=1);
}
vis[u]=1;
ll na=ov[u];p=0;
for(int i=1;i<=m;i++){
if((1ll<<(i-1))&na){c[++p]=i;}
}
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++){
ll y=(na&ov[i]),z=0;
for(int j=1;j<=p;j++){
if((1ll<<(c[j]-1))&y){
z|=(1<<(j-1));
}
}
++a[z];
}
int l=(1<<p);
fwtand(a,l,1);
for(int i=0;i<l;i++)if(a[i]*2>=n&&pc[i]>ans){
ans=pc[i];
memset(res,0,sizeof(res));
for(int j=1;j<=p;j++)if(i&(1<<(j-1))){
res[c[j]]=1;
}
}
}
for(int i=1;i<=m;i++){printf("%d",res[i]);}
}
int main(){
//int t;
//scanf("%d",&t);
//while(t--)
f1();
return 0;
}
/*
3 4 3
1000
0110
1001
*/