容斥原理学习笔记

普通容斥

解说

通俗地来说几个集合并集的大小,就是所有单个集合大小之和减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分……

\(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\n",ans);
	return 0;
}

\(Min-Max\)容斥

解说

简而言之就一个式子:

(下面大写字母均表示集合,\(| \ \ |\)表示集合大小)

\[\max(S)= \sum_{T\subset S,T \neq \emptyset} \min(T)(-1)^{|T|+1} \]

\[\min(S)= \sum_{T\subset S,T \neq \emptyset} \max(T)(-1)^{|T|+1} \]

简单解释一下,假设现在我们非常傻,不会取\(\max\)\(\min\))这一运算而只会取\(\min\)\(\max\)),那么我们就可以借助\(Min-Max\)容斥间接求出我们需要的值。

手模一组示例:

假设\(S=\{ 1,2,3\}\),我们想利用每个非空自己的最小值求其最大值。

枚举其非空子集:

\(\{ 1 \}\),贡献为\(1 \times (-1)^2=1\)

\(\{ 2 \}\),贡献为\(2 \times (-1)^2=2\)

\(\{ 3 \}\),贡献为\(3 \times (-1)^2=3\)

\(\{ 1,2 \}\),贡献为\(1 \times (-1)^3=-1\)

\(\{ 1,3 \}\),贡献为\(1 \times (-1)^3=-1\)

\(\{ 2,3 \}\),贡献为\(2 \times (-1)^3=-2\)

\(\{ 1,2,3\}\),贡献为\(1 \times (-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 \%\)的数据,\(N\le5\)

对于\(100 \%\)的数据,\(N \le 25 ,0 < Wi \le 10^9 ,0 < Pi \le 1\text{且}\sum P_i \le 1\)

解说

首先,肯定是所有礼物都全部买下的时候喜悦值最大,所以第一问白送分,同时问题转化为期望买几次可以把所有礼物全部买齐。

本来似乎是有概率\(DP\)做法的,但是加强版\(n \le 25\)的数据范围让我们只能用别的方法。考虑使用\(Min-Max\),我们要解决的最大问题就是现在\(min\)\(max\)分别代表什么。经过一番思考不难得出,对于一个集合,\(max\)现在代表将集合里的礼物全部买齐的期望,而\(min\)则代表买到集合中礼物任意一件的期望

显然,此时

\[\min(T)= \frac{1}{\sum_{i\in T} p_i} \]

所以我们枚举子集挨个计算即可,依然是二进制枚举或者递归均能解决。

代码

#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\n",sum);
	dfs(1,0,0);
	printf("%.3lf\n",ans);
	return 0;
}

幸甚至哉,歌以咏志。

posted @ 2020-09-27 18:04  DarthVictor  阅读(225)  评论(0编辑  收藏  举报
莫挨老子!