当前位置:首页 » 《随便一记》 » 正文

[题解]《算法零基础100讲》(第29讲) 容斥原理_友人苏的博客

2 人参与  2022年06月03日 10:01  分类 : 《随便一记》  评论

点击全文阅读


文章目录

  • 一. 容斥原理
    • 1.1 集合AUB
    • 1.2 集合AUBUC
  • 二. 知识回顾
  • 三. 课后习题
    • 3.1 丑数 III
    • 3.2 播放列表的数量

一. 容斥原理

  容斥原理在我们的高中数学中由了解过,就是两个集合A,B的并集的元素个数。同样,在大学里的概率论中也会讲解。

1.1 集合AUB

  在概率论中,我们有这样的一个公式:

A U B = A + B - (A∩B)

如图:

在这里插入图片描述
由于A与B有重叠部分,所以A+B后要减去一次重叠的部分(A∩B)。

1.2 集合AUBUC

A U B U C= A + B + C - (A ∩ B) - (A ∩ C) - (B ∩ C) + (A ∩ B ∩ C)

如图:
在这里插入图片描述
很明显,E,F,G三个部分分别重叠了两次,所以我所我们要减(A ∩ B) ,(A ∩ C) ,(B ∩ C),但也相当于减了三次G部分(A ∩ B ∩ C),所以我们还要在加一次(A ∩ B ∩ C)。

以此类推。

二. 知识回顾

推荐专栏:
算法零基础100讲》(第14讲)最小公倍数

  由于今日的题解需要用到最小公倍数,所以我们需要回顾一下,最小公倍数的计算方法。通过定理我们可以很明确的知道,a和b的最小公倍数就等于(a * b) / gcd(a,b)。

三. 课后习题

3.1 丑数 III

题目链接:
1201. 丑数 III

思路分析:
  题目要求我们找出第n个丑数,而题目给定丑数的定义为,能够被a,b,c,整除的数。所以我们最容易想到的就是暴力枚举,从1开始枚举,当遇到a,b,c的约数时,便记录下来,直到找到第n位。
代码如下:

int nthUglyNumber(int n, int a, int b, int c){
	int i = 1;
	int Ugly = 0;
	int cnt = 0;
	while (i){
		if (i % a == 0){
			cnt++;
		}
		else if (i % b == 0){
			cnt++;
		}
		else if (i % c == 0){
			cnt++;
		}
		if (cnt == n){
			Ugly = i;
			break;
		}
		i++;
	}
	return Ugly;
}

但题目给定的数据范围为[1,2000000000],很明显暴力的解法会超时,而这种时候我们就得想到一种时间复杂度更快的方法,所以我们很容易会想到O(logn)的二分法,但是这种解法与该题由上面联系呢?

接下来我们进行进一步的分析。

  1. 首先我们需要知道的是一个数 a 的约数中,小于n的约数个数为 [n / a]
  2. 然后我们还需知道,两个数的a,b的公约数中,小于n的公约数个数为 [n / lcm (a, b)] ,其中lcm是最小公倍数。

  现在我们假设ab , ac , bc分别为a和b,a和c,b和c的最小公倍数,abc为a,b,c的最小公倍数,假设他们不大于n丑数有m个,则m = n / a + n / b + n / c - n / ab - n / ac - n / bc + n / abc。
  有了上面的m,我们在结果区间[1,2000000000]上取中间值mid,计算不大于mid的丑数有m个,我们便可以对m与题目所给的n(这里的n是题目给的n)进行比较,如果m < n则说明小于mid的丑数还不够n个,那么我们就需将mid增大,便将区间转化为[mid + 1,2000000000],在从中找出新的mid,继续进行比较,直到区间重合,此时的区间值就是我们所求的第n个丑数的值。

代码如下:

#define ll long long
//计算表最大公约数
int gcd(int a, int b){
    return !b ? a : gcd(b, a % b);
}
//计算最小公倍数
ll lcm(int a, int b){
    return (ll)a / gcd(a, b) * b;
}

int nthUglyNumber(int n, int a, int b, int c){
    ll ab = lcm(a, b);//a 和 b的最小共倍数
    ll ac = lcm(a, c);//a 和 c 的最小共倍数
    ll bc = lcm(b, c);//b 和 c 的最小公倍数
    ll abc = lcm(lcm(a, b), c);//a, b, c的最小公倍数
    int left = 1, right = 2000000000;
    while(left < right){
        ll mid = left + (right - left) / 2;
        //不大于mid的约数有多少个
        ll m = mid/a+mid/b+mid/c-mid/ab-mid/ac-mid/bc+mid/abc;
        if(m < n){
            left = mid + 1;
        }
        else{
            right = mid;
        }
    }
    return left;
}

在这里插入图片描述

3.2 播放列表的数量

题目链接:
920. 播放列表的数量

分析:
  这是一道动态规划题,题目给了我们三个参数,n首不同的歌,播放列表长度goal,播放过的歌经过k首后才能在次播放。
思路:

  1. 首先我们需定义一个数组dp[goal + 1][n + 1],dp[i][j]代表长度为 i 的播放列表中恰好包含 j 首不同的歌。
  2. 当我们要计算dp[ i ][ j ]时,我们是在dp[i - 1][j - 1]的基础上还剩下n - j + 1首歌未播放,所以我们可以从中任取一首。
  3. 又因为题目给定了一个k值,当播放一首歌后,在播放k首歌,即可重复播放该首歌,所以我们还需在dp[i - 1][ j ]的基础上再判断(j - k)是否大于0,如果大于0,则dp[i][j] += dp[i - 1][j] * (j - k)。

代码如下:

#define MOD 1000000007

int numMusicPlaylists(int n, int goal, int k){
    long long dp[goal+1][n+1];
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for(int i = 1; i <= goal; i++){
        for(int j = 1; j <= n; j++){
            dp[i][j] += dp[i - 1][j - 1] * (n - j + 1);
            dp[i][j] += dp[i - 1][j] * fmax(j - k, 0);
            dp[i][j] %= MOD;
        }
    }
    return dp[goal][n];
}

在这里插入图片描述
第三道题太难了,我争取吧(lll¬ω¬)


点击全文阅读


本文链接:http://zhangshiyu.com/post/41263.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1