Deltix Round, Spring 2021 D. Love-Hate(随机+动态规划 or 搜索)

17 篇文章 0 订阅
11 篇文章 0 订阅

题目链接:https://codeforces.com/contest/1523/problem/D

题目链接:https://codeforces.com/contest/1523/problem/D

题目大意:

n,(1<=n<=2*10^5)个朋友来聚会,总共有m(1<=p<=m<=60)个话题,每个朋友最多喜欢p(p<=15)个话题,你需要选择一些话题,使得喜欢所有你选择话题的人数大于等于总人数的一半。求最多可以选择多少个话题,输出一种方案。

题解:

个人的方法:(暴力搜索+剪枝)

搜索方法和求一个图的最大团的方法相同,只是判断的条件不同。

简述:mc[i]表示话题i,i+1,..m中,最多可以选择多少个话题。那么mc[i]=mc[i+1]mc[i]=mc[i+1]+1,对于后一种情况中,话题i一定可以加入mc[i+1]的某个最优方案中。

因此,我们可以从mc[m]开始依次往前求,并利用计算的mc[i]来剪枝。

我们需要用bitset来维护话题的感兴趣状态。

可过,花费的时间和官方解法花费的时间差不多。

代码如下:

#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次,都没有选择中最优方案中的玩家的概率为(1/2)^{50}

选择了一个玩家的作为种子以后,这个玩家最多喜欢p个话题,那么我们只需要在这p个方案中选择,可选择的话题数就降到了15个。

我们可以用cnt[state]表示某个话题选择方案喜欢的人数。

如何求解cnt[state]  ?

我们可以首先计算出每个玩家的喜欢的话题状态来初始化cnt[state]

然后从第一个话题开始,枚举所有状态更新cnt[state],具体来说,例如第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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值