码迷,mamicode.com
首页 > 其他好文 > 详细

『8.21 模拟赛』技能大赛

时间:2018-08-21 21:45:59      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:答案   cpp   黑名单   break   一个   选择   完全   集中   iostream   

题目描述

rsw因为迟到次数太多被列入黑名单,于是被派去参加陕西妇女儿童技能大赛,大赛中共安排了m个比赛项目,算上rsw在内,共有n位选手报名参加本次比赛。(如rsw,zrx,kh,ljm,cky,大耳朵图图,大头儿子等)

经过m场比赛,组委会发现,每个项目,有且仅有两个人实力超群。(比如穿针引线项目,rsw,ljm独领风骚,健美操项目,cky,cjy风姿绰约)。

现在,组委会要推选一些人去参加全国比赛,因为每个项目都必须有人擅长,所以推选的这些人,对于每一个项目,至少要有一个人擅长。

已知,选中每个人参加比赛是有一个费用a[i],比如选中了1,3,5三个人参加比赛,就要支付a[1]*a[3]*a[5]的费用。

现在的问题是:组委会所有可行选人方案的费用总和是多少?




解题思路

考场上只打了一个30分的暴力,枚举子集TAT

其实稍微再想一想就会发现一些更高分的做法,数据最大的n只有36,遇到这种 \(2^n\)不能过的题但是\(2^\frac{n}{2}\)能过的数据就要想到这般枚举,把整个点集分成两个部分,那么我们其实就是要选择一些点,使得所有的边都被覆盖到,也就是类似于最小点覆盖。我们就把边分成了三种:

1)只在左边的集合里

2)只在右边的集合里

3)一边在左边,一边在右边

我们分别枚举,复杂度会减小很多,但是这样仍然会被卡掉,那怎么办呐?

加入我们现在在左边选了一些点,这些点刚好能覆盖所有的只在左边的边。但是这时有一些横跨左右的边是没有没有被覆盖的,那么我们就知道了在右边的点集中至少要选哪些点了,剩下的就是算出所有可行的情况对答案的贡献,比如左边的状态现在是i,左边的答案是sum1[i],比如右边一定要选的点集是00111,那么符合条件的答案就是sum2[00111],sum2[01111],sum2[11111],sum2[10111] (注意:假如某种情况不能完全覆盖集合内的边,那么他的sum2的值为0),我们使用高维前缀和来计算右边的超集的答案,乘上左边的sum1[i]就好了。




代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#define LL long long
using namespace std;
const int maxn=40;
LL sum1[1048576],sum2[1048576];
int a[maxn],bian[maxn];
vector<int>v[maxn];
bitset<maxn*maxn>map[maxn],hh,fuck1,fuck2;
int main(){
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    int l=(n>>1),r=n-l;
    for(register int i=1;i<=n;i++)scanf("%d",&a[i-1]);
    for(register int i=1,f,t;i<=m;i++){
        scanf("%d%d",&f,&t);
        f--,t--;
        v[f].emplace_back(t);
        v[t].emplace_back(f);
        if(f<l&&t<l)fuck1[i]=1;
        if(f>=l&&t>=l)fuck2[i]=1;
        map[f][i]=map[t][i]=1;
    }
    for(register int i=0;i<(1<<l);i++){
        hh.reset();
        LL tmp=1;
        for(register int j=0;j<l;j++){
            if(i&(1<<j)){
                hh|=map[j];
                tmp=(tmp*a[j])%q;
            }
        }
        bool x=1;
        for(register int j=1;j<=m;j++){
            if(fuck1[j]&&(!hh[j]))x=0;
            if(!x)break;
        }
        if(x)sum1[i]=tmp;
    }
    for(register int i=0;i<(1<<r);i++){
        hh.reset();
        LL tmp=1;
        for(register int j=0;j<r;j++){
            if(i&(1<<j)){
                hh|=map[j+l];
                tmp=(tmp*a[j+l])%q;
            }
        }
        bool x=1;
        for(register int j=1;j<=m;j++){
            if(fuck2[j]&&(!hh[j]))x=0;
            if(!x)break;
        }
        if(x)sum2[i]=tmp;
    }
    for(register int i=0;i<r;i++){
        for(register int j=0;j<(1<<r);j++)
            if(!(j&(1<<i)))sum2[j]=(sum2[j]+sum2[j|(1<<i)])%q;
    }
    LL ans=0;
    for(register int i=0;i<(1<<l);i++){
        if(!sum1[i])continue;
        LL ss=0;
        for(register int j=0;j<l;j++){
            if(!(i&(1<<j))){
                for(register int k=0;k<(int)v[j].size();k++){
                    if(v[j][k]<l)continue;
                    ss|=(1<<(v[j][k]-l));
                }
            }
        }
        ans=((sum1[i]*sum2[ss])%q+ans)%q;
    }
    cout<<ans<<endl;
}

『8.21 模拟赛』技能大赛

标签:答案   cpp   黑名单   break   一个   选择   完全   集中   iostream   

原文地址:https://www.cnblogs.com/Fang-Hao/p/9514339.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!