SOS dp+randomization
D. Love-Hate
题意:找一个最大的硬币集合,满足:n(1e5)个中至少n/2个人有全部这些硬币。(答案大小不超过15)
思路:
看到n/2:考虑randomization。随机选30个不重复的人,都不属于某个大小为n/2的集合的概率为1/{2^30}。
于是我们可以随机选一个人。认为这个人在最优解中。
那么最大的硬币集合肯定是这个人喜欢的硬币的子集。
我们把这个人喜欢的硬币离散化(设为sz)。
然后遍历所有sz的子集。找到{满足【n/2个人都有这子集】的集合中}大小最大的那一个。反离散化就是答案。
发现如果遍历子集,再遍历人,会有
30
×
2
15
×
1
e
5
30\times 2^{15}\times 1e5
30×215×1e5这么大,会TLE。
考虑如何优化。有一个算法叫做SOS,这个算法是用来计算:
∀
x
,
s
u
m
[
x
]
=
∑
y
⊆
x
a
[
y
]
{\forall x},sum[x]=\sum_{y\subseteq x} a[y]
∀x,sum[x]=∑y⊆xa[y]的。
//memory optimized, super easy to code.
for(int i = 0; i<(1<<N); ++i)
F[i] = A[i];
for(int i = 0;i < N; ++i) for(int mask = 0; mask < (1<<N); ++mask){
if(mask & (1<<i))
F[mask] += F[mask^(1<<i)];
}
时间复杂度为
N
2
N
N2^N
N2N
我们现在考虑的是,对于每个子集,统计有这个子集的人的数目。我们先得到该人的集合,该集合的人++。然后再讲该集合的子集全部++。那么就是对每一个集合,遍历其子集的问题了。只不过这一次更新的是子集。
反过来就可以了。
for(int i=0;i<sz;++i)
for(int j=0;j<(1<<sz);++j){
if(j&(1<<i)) cnt[j^(1<<i)] += cnt[j];
}
总时间复杂度为 i t e r ( 2 p p + n ) iter(2^p p+n) iter(2pp+n)即 30 × ( 2 15 × 15 + 1 e 15 ) 30\times (2^{15}\times 15+1e15) 30×(215×15+1e15)。
#include<bits/stdc++.h>
using namespace std;
#define mp(a, b) make_pair(a,b)
#define vi vector<int>
#define mii map<int,int>
#define mpi map<pair<int,int>,int>
#define vp vector<pair<int,int> >
#define pb(a) push_back(a)
#define fr(i, n) for(i=0;i<n;i++)
#define rep(i, a, n) for(i=a;i<n;i++)
#define F first
#define S second
#define endl "\n"
#define Endl "\n"
#define fast std::ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define mod 1000000007
#define dom 998244353
#define sl(a) (int)a.length()
#define sz(a) (int)a.size()
#define all(a) a.begin(),a.end()
#define pii pair<int,int>
#define mini 2000000000000000000
#define time_taken 1.0 * clock() / CLOCKS_PER_SEC
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
//const long double pi = acos(-1);
//mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
//primes for hashing 937, 1013
template<typename T, typename U>
static inline void amin(T &x, U y) {
if (y < x)
x = y;
}
template<typename T, typename U>
static inline void amax(T &x, U y) {
if (x < y)
x = y;
}
#define ll long long
int main() {
fast;
int n,m,p;cin>>n>>m>>p;
vector<ll> a(n);
for(int i=0;i<n;++i){
string s;
cin>>s;
for(int j=0;j<m;++j){
if(s[j]=='1')
a[i] |= (1ll<<j);
}
}
vector<int> order(n);
iota(order.begin(),order.end(),0);
shuffle(order.begin(),order.end(),rng);
int best = 0;
string ans(m,'0');
for(int it=0;it<min(n,30);++it){
int me=order[it];
vector<int>bits; // 存位置
for(int i=0;i<m;++i){
if((a[me]>>i)&1)
bits.pb(i);
}
int sz = (int)bits.size();
vector<int> cnt(1<<sz);
for(int i=0;i<n;++i){
int u=0;
for(int j=0;j<sz;++j){
if((a[i]>>bits[j])&1) u|=(1<<j);
}
cnt[u]++;
}
for(int i=0;i<sz;++i)
for(int j=0;j<(1<<sz);++j){
if(j&(1<<i)) cnt[j^(1<<i)] += cnt[j];
}
for(int i=0;i<(1<<sz);++i){
// 找答案
if(2*cnt[i] >= n && __builtin_popcount(i) > best){
best = __builtin_popcount(i);
ans = string(m,'0');
for(int j=0;j<sz;++j){
if(i&(1<<j)) ans[bits[j]] = '1';
}
}
}
}
cout<<ans<<endl;
return 0;
}