1. 程式人生 > >『8.21 模擬賽』技能大賽

『8.21 模擬賽』技能大賽

gist map res 完全 line scanf namespace ont ++

題目描述

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 模擬賽』技能大賽