第 45 届ICPC 亚洲网上区域赛模拟赛 A. Easy Equation(容斥原理+Hard版本)

点这里进入我的博客,获得更好的体验!

搜索框中搜索这篇博客即可,已做迁移,支持一下咯~

原题链接(需要登录)
在这里插入图片描述

题意:

给你x y z k的范围,找到满足式子x+y+z = k 的个数

思路:

我们考虑这样一个问题:
有n个非零整数 x 1 , x 2 , x 3 . . . x n x_1,x_2,x_3...x_n x1,x2,x3...xn 分别给出n个数的取值范围,求满足 x 1 + x 2 + x 3 + . . . + x n = M x_1+x_2+x_3+...+x_n=M x1+x2+x3+...+xn=M的方案数。
解法:
先考虑理想情况:n个数取值范围无限大,此时 x i ≥ 0 x_i \geq0 xi0,于是我们令 y 1 = x 1 + 1 , y 2 = x 2 + 1... y n = x n + 1 y_1 = x_1+1,y_2 = x_2+1...y_n=x_n+1 y1=x1+1y2=x2+1...yn=xn+1,那么问题就转换成求 y 1 + y 2 + . . . + y n = M + N y_1+y_2+...+y_n=M+N y1+y2+...+yn=M+N的方案数,此时 y i ≥ 0 y_i \geq0 yi0,故我们用隔板法:在M+N-1个空隙中插入N-1个板,故方案数为: C M + N − 1 N − 1 C_{M+N-1}^{N-1} CM+N1N1,但由于实际情况中, x 1 ≤ A 1 , x 2 ≤ A 2 . . . x n ≤ A n x_1\leq A_1,x_2\leq A_2...x_n\leq A_n x1A1,x2A2...xnAn,由于直接求满足限制条件的方案很难,那我们就尝试求其补集,再用全集(就是没有限制条件时的方案数)减去即可。设不满足 x i ≤ A i x_i \leq A_i xiAi的方案的集合为 S i S_i Si,则答案为 C M + N − 1 N − 1 − ∣ S 1 ∪ S 2 . . . ∪ S n ∣ C_{M+N-1}^{N-1}-|S_1 \cup S_2 ... \cup S_n| CM+N1N1S1S2...Sn,运用容斥原理,答案为 C M + N − 1 N − 1 − ∣ S 1 ∣ − ∣ S 2 ∣ − . . . − ∣ S n ∣ + ∣ S 1 ∩ S 2 ∣ + ∣ S 2 ∩ S 3 ∣ + . . . − ∣ S 1 ∩ S 2 ∩ S 3 ∣ − . . . C_{M+N-1}^{N-1}-|S_1|-|S_2|-...-|S_n|+|S1 \cap S_2|+|S_2 \cap S_3|+...-|S_1 \cap S_2 \cap S_3|-... CM+N1N1S1S2...Sn+S1S2+S2S3+...S1S2S3...(具有查百度,就是一个容斥展开式)。对于 ∣ S i ∣ |S_i| Si其实就是先取出 A i + 1 A_i+1 Ai+1 y i y_i yi(这样的操作使得不满足第i个限制条件,符合 S i S_i Si的定义),然后就变成了在剩下的 M + N − 1 − ( A i + 1 ) M+N-1-(A_i+1) M+N1Ai+1个空隙中插入N-1个隔板,即 C M + N − 2 − ( A i + 1 ) N − 1 C_{M+N-2-(A_i+1)}^{N-1} CM+N2Ai+1N1同理 ∣ S i ∩ S j ∣ = C M + N − 2 − ( A i + 1 ) − ( A j + 1 ) N − 1 |S_i \cap S_j| =C_{M+N-2-(A_i+1)-(A_j+1)}^{N-1} SiSj=CM+N2Ai+1(Aj+1)N1
a n s = C M + N − 1 N − 1 − ∑ i = 1 N C M + N − 2 − ( A i + 1 ) N − 1 + ∑ i < j N C M + N − 2 − ( A i + 1 ) − ( A j + 1 ) N − 1 − . . . + . . . ans=C_{M+N-1}^{N-1}-\displaystyle \sum_{i = 1}^{N}C_{M+N-2-(A_i+1)}^{N-1}+\displaystyle \sum_{i <j }^{N}C_{M+N-2-(A_i+1)-(A_j+1)}^{N-1}-...+... ans=CM+N1N1i=1NCM+N2Ai+1N1+i<jNCM+N2Ai+1(Aj+1)N1...+...

现在回到这道题就很容易了,直接给出式子: a n s = C k + 3 − 1 2 − C k + 3 − 1 − ( a + 1 ) 2 − C k + 3 − 1 − ( b + 1 ) 2 − C k + 3 − 1 − ( c + 1 ) 2 + C k + 3 − 2 − ( a + 1 ) − ( b + 1 ) 2 + C k + 3 − 1 − ( a + 1 ) − ( c + 1 ) 2 + C k + 3 − 1 − ( b + 1 ) − ( c + 1 ) 2 − C k + 3 − 1 − ( a + 1 ) − ( b + 1 ) − ( c + 1 ) 2 ans=C_{k+3-1}^{2}-C_{k+3-1-(a+1)}^{2}-C_{k+3-1-(b+1)}^{2}-C_{k+3-1-(c+1)}^{2}+C_{k+3-2-(a+1)-(b+1)}^{2}+C_{k+3-1-(a+1)-(c+1)}^{2}+C_{k+3-1-(b+1)-(c+1)}^{2}-C_{k+3-1-(a+1)-(b+1)-(c+1)}^{2} ans=Ck+312Ck+31(a+1)2Ck+31(b+1)2Ck+31(c+1)2+Ck+32(a+1)(b+1)2+Ck+31(a+1)(c+1)2+Ck+31(b+1)(c+1)2Ck+31(a+1)(b+1)(c+1)2

由于这道题的k有范围要求,直接遍历一遍所以情况把每种情况方案数加起来就行,给出代码:

LL C(int n, int m) {
    if(n <= 0) return 0;
    LL res = 1;
    for(int i = n; i >= n-m +1; i--) {
        res *= i;
    }
    for(int i = 2; i<=m; i++){
        res /= i;
    }
    return res;
}
int main(int argc, char const *argv[]) {
    ios::sync_with_stdio(false); 
    int a ,b, c, d;
    LL ans = 0;
    cin >> a >> b >> c >> d;
    for(int i = 0; i <= d; i++){
        ans+= C(i+2,2) - C(i+2-a-1,2) - C(i+2-b-1,2) - C(i+2-c-1,2) + C(i+2-a-b-2,2) + C(i+2-a-c-2,2) + C(i+2-b-c-2,2) - C(i+2-a-b-c-3,2);
    }
    cout << ans << endl;
    return 0;
}

Hard版本题目:

Acwing 214.Devu和鲜花
这道题就是更一般的情况了,要用到上面推导的结论。这道题N的数据范围很小,我们把每一个条件看成一个二进制位,如果是1或0就代表遵守或不遵守这个条件,奇数个1就减,偶数个1就加,举例: S 2 ∩ S 4 ∩ S 7 S_2 \cap S_4 \cap S_7 S2S4S7可以表示为0101001。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int mod = 1e9+7;
LL m,A[30];
int qmi(int a, int b, int p){//快速幂求逆元
    int res = 1;
    while(b) {
        if(b&1) res = (LL) res * a % p;
        b >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}
int down = 1;
int C(LL a, LL b){
    if(a < b) return 0;
    int up = 1;
    for(LL i = a;  i >= a - b + 1; i--) up =  i % mod * up % mod;
    return (LL)up * down % mod;//由于数据大会超LL要先取模,利用逆元化除为乘(除法不能先取模)
}

int main(){
    int n;
    cin >> n >> m;
    for(int i = 1;  i <= n-1; i++) down = (LL)down * i % mod;
    down = qmi(down, mod-2, mod);//费马小定理求逆元
    for(int i = 0; i < n; i++){
        cin >> A[i];
    }
    int ans = 0;
    for(int i = 0; i < 1 << n; i++){
        LL a = m+n-1, b = n-1;
        int flag = 1;
        for(int j = 0; j < n; j++){
            if(i >> j & 1){
                flag *=-1;
                a -= A[j]+1;
            }
        }
        ans = (ans + flag * C(a,b))%mod;
    }
    cout << (ans + mod) %mod << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值