题目描述
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;
}
原文地址:https://www.cnblogs.com/Fang-Hao/p/9514339.html