『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;
}

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

时间: 2024-08-29 08:21:17

『8.21 模拟赛』技能大赛的相关文章

『8.21 模拟赛』Victory

题目描述 迟到大王rsw喜欢V这个字母,因为V代表着victory,当一个数字,从左向右数的时候,没有出现过先递减,再递增的情况,就被称作Victory数. 也就是说,这个数字可以是递增的,也可以是递减的,也可以是先递减再递增的,这个过程中可以出现相邻数字相等的情况,但是,就是不能出现过先递减再递增的情况. 问题是:给定n,问:1~n之间有多少个victory数. 解题思路 我们应该一眼就能看出这是一道数位dp题,一开始用3维dp写,状态不够写挂了...后来改用4维就A了. 我们用dp[ i ]

『8.21 模拟赛』冒泡排序 II

题目描述 前一天的冒泡排序对rsw来说太简单了,所以又有了冒泡排序2,给定n,k,q,问:有多少个不同的1~n的排列,能够使得,冒泡排序k趟后,得到一个几乎正确的序列. 一个几乎正确的序列指的是:它的最长上升子序列的长度至少是n-1. 解题思路 思路就是没有思路.... 昨天刚刚做过一道冒泡排序的题,有一个结论在这里 每个位置上的数最远是由前面的第k个位置转移过来的,并且题目中要求最长上升子序列最短是n-1的长度,所以只有两种情况: 1) 最终是从小到大排序好的 2)最终是从小到大排序好的序列中

『8.20 模拟赛』旋转的多边形

题目链接戳着里!! 题目描述 解题思路 显然,多边形滚动的时候,指定的点一定是绕着某一个顶点旋转的,旋转的半径就是点到顶点的距离,角度就是顶点所在脚的外角. 如下图所示: 那么我们的问题就转化成了求dis和θ了. dis很简单,只要勾股定理就好了. 那θ呢?也很简单喽,只要链接当前顶点的相邻的两个顶点,运用余弦定理求就好啦,注意一下角度值和弧度制就可以了. (不会余弦定理的小伙伴戳这里==>  Wikipedia & 百度百科) 代码 1 #include<iostream> 2

『8.25 模拟赛』外卖 (atcoder 100e)

题目链接 题目描述 众所周知,\(cky\)喜欢点外卖. 已知可选的商品有\(n\)种,\(cky\)由于胃容量问题只能点两份(不能一种点两份).\(cky\)要在防止营养过剩的情况下选择美味度最高的搭配. 具体的,对于每第\(i\)个商品,\(i\)正好是其营养成分,\(s_i\)表示其美味度(商品从\(0\)开始编号). 对于每种搭配\((a,b)\),其营养程度为(\(a|b\)其中\(|\)表示二进制下的按位或),其美味度为\(s_a+s_b\). 即\(cky\)要选择满足\(a|b\

『8.20 模拟赛』冒泡排序

题目描述 给定n,k,和一个长度为n的序列,请输出这个序列冒泡排序k次之后的结果. 解题思路 我们观察上面给出的伪代码,可以发现这是一段把代码排序成升序的代码,那我们来考虑一下冒牌排序的几个特征. 一个大的数要向右交换,但是一次交换之后就可以换很多位置,所以换一次就不知道跑到哪里去了,所以很难维护. 一个小的数每次最多和它左边的更大的数交换一次,所以一次最多向左边跑一个位置,比大数不知道好维护到那里去了. 进一步思考,k次交换之后,最多可以向左跑k步,也就是说k+1位置的数最远能跑到1的位置上来

『8.24 模拟赛』ranwen的服务器

题目链接戳这里n(*≧▽≦*)n 题目描述 众所周知,ranwen建造出了强大的服务器网,规模十分庞大,有n个服务器,组成一个树形结构,1号点是总站,但是有时候可能会因为主机被回收,机房断电等事故造成服务器各种GG,现在有m个事件,可能为:1.查询x到根路径上第一个已经挂掉的服务器传输路径 2.x到y路径上的服务器传输路径全挂了.(不存在则输出0) 解题思路 上来先是树剖,然而只过了样例,爆0... 题面上提示了正解是并查集,但是并没有想出来,听完题解瞬间懂了.... 并查集,当然最方便的是合并

『8.21考试题解及反思』

UNO Description 良心出题人Magolor找到了你,想要和你一起玩桌(mo)游(ni). Magolor: "杀蚂蚁?猪国杀?斗地主?麻将?立体图?哪一个好啊?" 你: "毒瘤出题人!" Magolor伤心了--"我应该给人留下一个良心出题人的印象啊!" 于是Magolor选择了众所周知的UNO.整个周游只使用UNO牌,但完全不按照UNO的规则来打.牌局有3位玩家(你.Magolor.Magolor的好朋友TTL): 每个人将会摸到

【BZOJ】【2741】【FOTILE模拟赛】L

可持久化Trie+分块 神题……Orz zyf & lyd 首先我们先将整个序列搞个前缀异或和,那么某一段的异或和,就变成了两个数的异或和,所以我们就将询问[某个区间中最大的区间异或和]改变成[某个区间中 max(两个数的异或和)] 要是我们能将所有[l,r]的答案都预处理出来,那么我们就可以O(1)回答了:然而我们并不能. 一个常见的折中方案:分块! 这里先假设我们实现了一个神奇的函数ask(l,r,x),可以帮我们求出[l,r]这个区间中的数,与x最大的异或值. 我们不预处理所有的左端点,我

10.30 NFLS-NOIP模拟赛 解题报告

总结:今天去了NOIP模拟赛,其实是几道USACO的经典的题目,第一题和最后一题都有思路,第二题是我一开始写了个spfa,写了一半中途发现应该是矩阵乘法,然后没做完,然后就没有然后了!第二题的暴力都没码QAQ 现在我来写解题报告了,有点饿了QAQ.. 第一题 题目 1: 架设电话线 [Jeffrey Wang, 2007] 最近,Farmer John的奶牛们越来越不满于牛棚里一塌糊涂的电话服务,于 是,她们要求FJ把那些老旧的电话线换成性能更好的新电话线.新的电话线架设 在已有的N(2 <=