普通容斥
解说
通俗地来说几个集合并集的大小,就是所有单个集合大小之和减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分……
(All in all),奇加偶减。
那么我们在实际操作的时候就可以枚举每一个子集((DFS)和二进制都可以),计算其大小,大小为奇数就加上其大小,否则减去其大小。
例题:八
题目
找出([a,b])中能被(8)整除却不能被其他一些数整除的数。
输入格式
第一行一个数(n),代表不能被整除的数的个数。
第二行(n)个数,中间用空格隔开。
第三行两个数(a,b),中间一个空格。
输出格式
一个整数,为([a,b])间能被(8)整除却不能被那 个数整除的数的个数。
样例输入
3
7764 6082 462
2166 53442
样例输出
6378
解说
够裸的了吧,没啥可说的……
#include<bits/stdc++.h>
using namespace std;
int n,a,b,num[15+3],ans;
typedef long long ll;
ll gcd(ll a,ll b){
if(!b) return a;
return gcd(b,a%b);
}
ll lcm(ll a,ll b){
ll g=gcd(a,b);
return a*b/g;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&num[i]);
scanf("%d%d",&a,&b);
ans=b/8-(a-1)/8;
for(int s=1;s<=(1<<n)-1;s++){
int cnt=0;
ll LCM=8;
for(int i=1;i<=n;i++){
if(s&(1<<i-1)){
cnt++;
LCM=lcm(LCM,num[i]);
}
}
if(cnt%2) ans-=b/LCM-(a-1)/LCM;
else ans+=b/LCM-(a-1)/LCM;
}
printf("%d
",ans);
return 0;
}
(Min-Max)容斥
解说
简而言之就一个式子:
(下面大写字母均表示集合,(| |)表示集合大小)
简单解释一下,假设现在我们非常傻,不会取(max)((min))这一运算而只会取(min)((max)),那么我们就可以借助(Min-Max)容斥间接求出我们需要的值。
手模一组示例:
假设(S={ 1,2,3}),我们想利用每个非空自己的最小值求其最大值。
枚举其非空子集:
({ 1 }),贡献为(1 imes (-1)^2=1)
({ 2 }),贡献为(2 imes (-1)^2=2)
({ 3 }),贡献为(3 imes (-1)^2=3)
({ 1,2 }),贡献为(1 imes (-1)^3=-1)
({ 1,3 }),贡献为(1 imes (-1)^3=-1)
({ 2,3 }),贡献为(2 imes (-1)^3=-2)
({ 1,2,3}),贡献为(1 imes (-1)^4=1)
加和一下会发现它等于(3),正好是最大值。
但是这玩意儿貌似好鸡肋啊……其实它在算期望的时候会非常有用。直接借助例题来说。
例题:礼物加强版
题目
夏川的生日就要到了。作为夏川形式上的男朋友,季堂打算给夏川买一些生日礼物。商店里一共有种礼物。夏川每得到一种礼物,就会获得相应喜悦值(W_i)(每种礼物的喜悦值不能重复获得)。每次,店员会按照一定的概率(P_i)(或者不拿出礼物),将第(i)种礼物拿出来。季堂每次都会将店员拿出来的礼物买下来。没有拿出来视为什么都没有买到,也 算一次购买。
众所周知,白毛切开都是黑的。所以季堂希望最后夏川的喜悦值尽可能地高。
求夏川最后最大的喜悦值是多少,并求出使夏川得到这个喜悦值,季堂的期望购买次数。
输入格式
第一行,一个整数(N),表示有(N)种礼物。
接下来(N)行,每行一个实数(P_i)和正整数(W_i),表示第(i)种礼物被拿出来的概率和可以获得喜悦值。
输出格式
第一行,一个整数表示可以获得的最大喜悦值。
第二行,一个实数表示获得这个喜悦值的期望购买次数,保留(3)位小数。
样例
样例输入
3
0.1 2
0.2 5
0.3 7
样例输出
14
12.167
数据范围与提示
对于(10 \%)的数据,(N = 1)
对于(30 \%)的数据,(Nle5)
对于(100 \%)的数据,(N le 25 ,0 < Wi le 10^9 ,0 < Pi le 1 ext{且}sum P_i le 1)
解说
首先,肯定是所有礼物都全部买下的时候喜悦值最大,所以第一问白送分,同时问题转化为期望买几次可以把所有礼物全部买齐。
本来似乎是有概率(DP)做法的,但是加强版(n le 25)的数据范围让我们只能用别的方法。考虑使用(Min-Max),我们要解决的最大问题就是现在(min)和(max)分别代表什么。经过一番思考不难得出,对于一个集合,(max)现在代表将集合里的礼物全部买齐的期望,而(min)则代表买到集合中礼物任意一件的期望。
显然,此时
所以我们枚举子集挨个计算即可,依然是二进制枚举或者递归均能解决。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int lzw=25+3;
double p[lzw],ans;
ll sum;
int n;
void dfs(int loc,double res,int ch){
if(loc==n+1){
if(!ch) return;
ans+=1.0/res*((ch%2)?1:-1);
return;
}
dfs(loc+1,res,ch);
dfs(loc+1,res+p[loc],ch+1);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int tmp;
scanf("%lf%d",&p[i],&tmp);
sum+=tmp;
}
printf("%lld
",sum);
dfs(1,0,0);
printf("%.3lf
",ans);
return 0;
}
幸甚至哉,歌以咏志。