题目链接:https://codeforces.com/contest/1523/problem/D
题目链接:https://codeforces.com/contest/1523/problem/D
题目大意:
个朋友来聚会,总共有
个话题,每个朋友最多喜欢
个话题,你需要选择一些话题,使得喜欢所有你选择话题的人数大于等于总人数的一半。求最多可以选择多少个话题,输出一种方案。
题解:
个人的方法:(暴力搜索+剪枝)
搜索方法和求一个图的最大团的方法相同,只是判断的条件不同。
简述:表示话题
中,最多可以选择多少个话题。那么
或
,对于后一种情况中,话题
一定可以加入
的某个最优方案中。
因此,我们可以从开始依次往前求,并利用计算的
来剪枝。
我们需要用来维护话题的感兴趣状态。
可过,花费的时间和官方解法花费的时间差不多。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int nn =210000;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 100000007;
int n,m,p;
bitset<nn> bit[65];
bool g[65][65];
int ans;
int mc[65];
int lis[65][65];
int len[65];
bool found;
LL ans_state;
void dfs(int siz,LL state,bitset<nn>b)
{
if(len[siz]==0)
{
if(siz>ans)
{
ans=siz;
found=true;
ans_state=state;
}
return;
}
for(int i=0;i<len[siz]&&!found;i++)
{
if(siz+len[siz]-i<=ans)
break;
int u=lis[siz][i];
if(siz+mc[u]<=ans)
break;
len[siz+1]=0;
bitset<nn> bu=b&bit[u];
if(bu.count()<(n+1)/2)
continue;
for(int j=i+1;j<len[siz];j++)
{
int v=lis[siz][j];
if(g[u][v]&&(bu&bit[v]).count()>=(n+1)/2)
{
lis[siz+1][len[siz+1]++]=v;
}
}
dfs(siz+1,state|(LL(1)<<u),bu);
}
}
void max_cluster()
{
ans_state=0;
mc[m-1]=ans=1;
for(int i=m-2;i>=0;i--)
{
len[1]=0;
for(int j=i+1;j<m;j++)
{
if(g[i][j])
{
lis[1][len[1]++]=j;
}
}
found=false;
dfs(1,LL(1)<<i,bit[i]);
mc[i]=mc[i+1];
if(found)
mc[i]++;
}
}
int main()
{
cin>>n>>m>>p;
string s;
for(int i=0;i<n;i++)
{
cin>>s;
for(int j=0;j<m;j++)
{
if(s[j]=='1')
{
bit[j].set(i,1);
}
}
}
memset(g,false,sizeof(g));
for(int i=0;i<m;i++)
{
for(int j=i+1;j<m;j++)
{
if((bit[i]&bit[j]).count()>=(n+1)/2)
{
g[i][j]=g[j][i]=true;
}
}
}
max_cluster();
if(ans_state==0)
{
for(int i=0;i<m;i++)
{
if(bit[i].count()>=(n+1)/2)
{
ans_state|=(LL(1)<<i);
break;
}
}
}
for(int i=0;i<m;i++)
{
if(ans_state&(LL(1)<<i))
cout<<1;
else
cout<<0;
}
cout<<endl;
return 0;
}
官方解法:(随机法+动态规划)
由于最终选择的方案中一定有超过一半的人喜欢,所以我们可以随机选择一个人的喜欢的话题作为种子,有一半的可能性这个玩家的话题包含最终的最优方案。我们随机选择50次,都没有选择中最优方案中的玩家的概率为。
选择了一个玩家的作为种子以后,这个玩家最多喜欢个话题,那么我们只需要在这
个方案中选择,可选择的话题数就降到了15个。
我们可以用表示某个话题选择方案喜欢的人数。
如何求解 ?
我们可以首先计算出每个玩家的喜欢的话题状态来初始化
然后从第一个话题开始,枚举所有状态更新,具体来说,例如第i个话题
if((state>>i)&1)
cnt[state^(1<<i)]+=cnt[state]
详情见代码:
#include<bits/stdc++.h>
using namespace std;
const int nn =210000;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 100000007;
int n,m,p;
string s[nn];
vector<int> ve[nn];
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
int main()
{
cin>>n>>m>>p;
for(int i=0;i<n;i++)
{
cin>>s[i];
for(int j=0;j<m;j++)
{
if(s[i][j]=='1')
{
ve[i].push_back(j);
}
}
}
vector<int>id(n);
iota(id.begin(),id.end(),0);
shuffle(id.begin(),id.end(),rnd);
string ans(m,'0');
int best=0;
for(int i=0;i<min(n,50);i++)
{
int u=id[i];
vector<int> cnt(1<<ve[u].size());
for(int j=0;j<n;j++)
{
int v=0;
for(int k=0;k<int(ve[u].size());k++)
{
if(s[j][ve[u][k]]=='1')
{
v|=(1<<k);
}
}
cnt[v]++;
}
for(int j=0;j<int(ve[u].size());j++)
{
for(int k=0;k<(1<<ve[u].size());k++)
{
if(k&(1<<j))
{
cnt[k^(1<<j)]+=cnt[k];
}
}
}
for(int j=0;j<(1<<ve[u].size());j++)
{
if(2*cnt[j]>=n && __builtin_popcount(j)>best)
{
best = __builtin_popcount(j);
ans = string(m,'0');
for(int k=0;k<int(ve[u].size());k++)
{
if(j&(1<<k))
ans[ve[u][k]]='1';
}
}
}
}
cout<<ans<<endl;
return 0;
}