『8.21 模擬賽』技能大賽
題目描述
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 模擬賽』技能大賽