暑假集训日记——8.8(容斥+概率)

本文通过解析多个竞赛题目,探讨了容斥原理在解决概率问题和算法设计中的应用,包括卡片收集的期望包数、可见树木的数量计算、整数集合的构造以及多项式系数奇数项的计数。这些实例展示了容斥原理在解决复杂问题时的有效性和普适性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

E - Card Collector
题意:
每包里面最多只有一张卡片(可能没有),要集齐n张不同的卡片(1<=n<=20)
问集齐 n n n张卡片要买的包数期望

题解:
设集齐第 i i i张的天数需要 d i di di
求解买 n n n张卡片需要的包数: d 1 U d 2 U d 3... U d n d1 U d2 U d3 ... U dn d1Ud2Ud3...Udn- 1 1 1,所以容斥公式求解

概率和期望:(可由无穷级数得证)
如果一件事发生的概率为 p i pi pi,那么第一次发生这件事次数期望为 1 / p i 1/pi 1/pi
同理,a和b这两件事发生的概率为 p 1 , p 2 p1,p2 p1,p2,则第一次发生某一件事发生的次数期望为 1 / ( p 1 + p 2 ) 1/(p1+p2) 1/(p1+p2)

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

const int MAXN = 3e5;
const int N = 22;
double p[N];
double dp[1<<N];
int n,cnt;

double IEP()
{
    double sum=0;
    for(int i=1;i<(1<<cnt);i++)///容斥原理
    {
        double f=-1,ans=0;
        for(int j=0;j<cnt;j++)///枚举每种质因数的组合
        {
            if(i&(1<<j))
            {
                ans+=p[j];
                f=f*(-1);
            }
        }
        sum=sum+(1.0/ans)*f;///(x/ans)表示:1-x中有ans因数的个数
    }
    return sum;
}

int main()
{
    while(~scanf("%d", &n))
    {

        for(int i = 0; i < n; i ++)
            scanf("%lf", &p[i]);
        double ans = 0.0;
        cnt=n;
        ans=IEP();
        printf("%.5lf\n", ans);
    }

}

F - Visible Trees
题意:
有许多树组成一个 m ∗ n m * n mn网格,网格从 ( 1 , 1 ) (1,1) (1,1)开始。农夫夏洛克站在 ( 0 , 0 ) (0,0) (0,0)点。他想知道他能看到多少棵树。如果两棵树和夏洛克站在一条线上,农民夏洛克只能看到离他最近的那棵树。
题解:
求解1到m和1到n有多少对互质的数,分解 m i n ( n , m ) min(n,m) min(n,m)的一个数,然后容斥求解互质即可

#include<cstdio>
#include<algorithm>
#define mp make_pair
#define maxn 10000000
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;

const int N=1e6+10;
const int MAXN=20010;
const int INF=0x3f3f3f3f;
const double eps=0.0000001;
const ll mod=998244353 ;
ll n,m,x,y,k,cnt,t;
ll prime[N];

void gets(ll x)
{
    ll s=sqrt(x);
    cnt=0;
    for(int i=2;i<=s;i++)
    {
        if(x%i==0)
            prime[cnt++]=i;
        while(x%i==0)
            x=x/i;
    }
    if(x!=1)
        prime[cnt++]=x;
}

ll IEP(ll x)
{
    if(x<0) return 0;
    ll sum=0;
    for(int i=1;i<(1<<cnt);i++)///容斥原理
    {
        ll f=-1,ans=1;
        for(int j=0;j<cnt;j++)///枚举每种质因数的组合
        {
            if(i&(1<<j))
            {
                ans=ans*prime[j];
                f=f*(-1);
            }
        }
        sum=sum+(x/ans)*f;///(x/ans)表示:1-x中有ans因数的个数
    }
    return x-sum;
}



int main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	scanf("%lld",&t);
	while(t--)
    {
        scanf("%d%d",&n,&m);
        if(n>m) swap(n,m);
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
            gets(i);
            ans+=IEP(m);
        }
        printf("%lld\n",ans);
    }

}

G - How many integers can you find
题意:
现在你得到一个 N , M − i n t e g e r s N, M-integers N,Mintegers集,你应该找出多少整数比 N N N,小,他们可以任何整数整除的。例如, N N N = 12 12 12,和 M − i n t e g e M-intege Minteger{ 2 , 3 2,3 2,3},所以有另一组{ 2 , 3 , 4 , 6 , 8 , 9 , 10 2,3,4,6,8,9,10 2,3,4,6,8,9,10},的所有整数集可以被 2 2 2 3 3 3整除。因此,您只需输出数字 7 7 7
题解:容斥
注意:因为是整除注意特判 0 0 0,以及M集合可能有重复的元素,所以当进行容斥的时候注意将选中的因子取最小公倍数,避免重复。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define mp make_pair
#define maxn 10000000
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;

const int N=1e6+10;
const int MAXN=20010;
const int INF=0x3f3f3f3f;
const double eps=0.0000001;
const ll mod=998244353 ;
ll n,m,x,y,k,cnt,t;
ll prime[N];

ll lcm(ll m,ll n)
{
    return m/__gcd(m,n)*n;
}

ll IEP(ll x)
{
    ll sum=0;
    for(int i=1;i<(1<<cnt);i++)///容斥原理
    {
        ll f=-1,ans=1;
        for(int j=0;j<cnt;j++)///枚举每种质因数的组合
        {
            if(i&(1<<j))
            {
                ans=lcm(ans,prime[j]);
                f=f*(-1);
            }
        }
        sum=sum+(x/ans)*f;///(x/ans)表示:1-x中有ans因数的个数
    }
    return sum;
}

int main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	while(~scanf("%lld%lld",&n,&m))
    {
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%lld",&x);
            if(x!=0) prime[cnt++]=x;
        }
        printf("%lld\n",IEP(n-1));
    }
}

题意:
F ( x ) F(x) F(x) = ( 1 + x ) a 1 (1+x)^{a_{1}} (1+x)a1 + ( 1 + x ) a 2 (1+x)^{a_{2}} (1+x)a2+ … + ( 1 + x ) a m (1+x)^{a_{m}} (1+x)am.中系数为奇数的项的个数
题解:
性质一:由 L u c a s Lucas Lucas定理可知, ( 1 + x ) n (1+x)^n (1+x)n 中奇数项的个数等于 2 ( n 的 二 进 制 表 示 中 1 的 个 数 ) 2^{(n的二进制表示中1的个数)} 2(n1).
性质二: ( 1 + x ) n (1 + x) ^ n (1+x)n中的系数中 所有奇系数之和等于偶系数之和等于 2 ( n − 1 ) 2^{(n-1)} 2(n1)
性质三:两个集合的交: 比如系数 w 1 , w 2 , p o s = w 1 w_1,w_2,pos=w_1 w1,w2,pos=w1& w 2 w_2 w2集合的交的个数是 2 p o s 的 二 进 制 里 1 的 个 数 2^{pos的二进制里1的个数} 2pos1
在这里插入图片描述
嗯这个 L u c a s Lucas Lucas定理的证明,没看懂,反正是这么个东西。
参考题解
集合 A , B , C A,B,C A,B,C表示不同次幂下,奇数系数项的个数
n u m ( A ∪ B ∪ C ) num(A∪B∪C) num(ABC)表示合并后一共有多少个不同的奇数系数项,这里的奇数系数指的合并前的。

举例来说:
( 1 + x ) 1 (1 + x) ^ 1 (1+x)1奇数项有 2项, ( 1 + x ) 2 (1 + x) ^ 2 (1+x)2奇数项有 2项, ( 1 + x ) 3 (1 + x) ^ 3 (1+x)3奇数项有 4项
因此 n u m ( A ∪ B ∪ C ) num(A∪B∪C) num(ABC)的值有4项。
当 集合合并偶数次的时候,相同次幂的奇数系数项自然变成偶数所以要减去,但是会多减,如下图的 10区域,所以还得加回来。
Alt因此:
F ( x ) F(x) F(x)中奇数系数项的个数= n u m ( A ∪ B ∪ C ) num(A∪B∪C) num(ABC)- n u m ( A ∩ B ) num(A∩B) num(AB)- n u m ( A ∩ C ) num(A∩C) num(AC)- n u m ( B ∩ C ) num(B∩C) num(BC)+ 3 ∗ n u m ( A ∩ B ∩ C ) 3*num(A∩B∩C) 3num(ABC)
进一步化简:
F ( x ) F(x) F(x)中奇数系数项的个数= n u m ( A ) + n u m ( B ) + n u m ( C ) − 2 ∗ n u m ( A ∩ B ) − 2 ∗ n u m ( A ∩ C ) − 2 ∗ n u m ( B ∩ C ) + 4 ∗ n u m ( A ∩ B ∩ C ) num(A)+num(B)+num(C) -2*num(A∩B)-2*num(A∩C)-2*num(B∩C) +4*num(A∩B∩C) num(A)+num(B)+num(C)2num(AB)2num(AC)2num(BC)+4num(ABC)

#include<cstdio>
#include<algorithm>
#include<cmath>
#define mp make_pair
#define maxn 10000000
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;

const int N=1e6+10;
const int MAXN=20010;
const int INF=0x3f3f3f3f;
const double eps=0.0000001;
const ll mod=998244353 ;
ll n,m,x,y,k,cnt,t,ans;
ll a[N];

ll get (ll x)
{//计算x的二级制位有多少个1
	return x==0?0:get(x-(x&-x))+1;  //(x&-x)是取出最低位的1
}

void dfs(ll pos,ll num,ll fla)
{
    ans+=(1ll<<get(num))*fla;//需要强制类型转换,精度
    for(ll i=pos+1;i<=n;i++)
        dfs(i,num&a[i],-2*fla);
}

int main()
{
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	scanf("%lld",&t);
	k=t;
	while(t--)
    {
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
        }
        ans=0;
        for(int i=1;i<=n;i++)//全排列
            dfs(i,a[i],1);
        printf("Case #%lld: %lld\n",k-t,ans);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值