LeetCode---117双周赛---容斥原理

题目列表

2928. 给小朋友们分糖果 I

2929. 给小朋友们分糖果 II

2930. 重新排列后包含指定子字符串的字符串数目

2931. 购买物品的最大开销

一、给小朋友们分糖果I

看一眼数据范围,如果没有啥其他想法思路就直接暴力,时间复杂度O(n^2)

思路:枚举前两个小朋友分得的合法糖果数,看第三个小朋友的糖果数是否符合条件

代码如下

class Solution {
public:
    int distributeCandies(int n, int limit) {
        int ans=0;
        for(int i=0;i<=min(n,limit);i++){
            for(int j=0;j<=min(n-i,limit);j++){
                ans+=(n-i-j<=limit);
            }
        }
        return ans;
    }
};

二、给小朋友们分糖果II

 

和上一题一样的题目,数据范围变大,不能暴力,需要优化时间复杂度,如何优化???

上一题思路是枚举两个元素,来看第三个元素是否符合条件,但其实我们只要枚举一个元素,就能知道剩余的糖果如何分配,才能合法,因为剩余两个元素的和是固定的

举个例子:如果剩余的糖果数为10,limit=3,分配的方式为:3和7,4和6,5和5,6和4,7和3,一共7-3+1=5种,即我们只要能算出第二个元素或第三个元素的取值范围,就能得到合法的方案数

设left为剩余糖果的数量,总共有三种情况

1.left<=limit,即剩下的糖果随便怎么分都行,共有left+1种方案,注意0和left也是一种方案

2.left>limit*2,即剩下的糖果无论怎么分,都会有一个人的糖果数>limit,方案数为0

3.limit<left<=limit*2,即有合法的分配方案,但不能随意分配,有限制,其中一个人的糖果数范围是[left-limit,limit],共有limit-(left-limit)+1种方案数

代码如下 

class Solution {
public:
    int distributeCandies(int n, int limit) {
        int ans=0;
        for(int i=0;i<=min(n,limit);i++){
            int left=n-i;
            if(left<=limit)
                ans+=left+1;
            else if(limit*2>=left){
                int x=left-limit;
                ans+=limit-x+1;
            }
        }
        return ans;
    }
};

这题还有一个O(1)时间复杂度的算法,利用容斥原理来做,不了解的可以去了解一下

这题可以转换为将n个球放入3个不同的盒子中,盒子可以为空,且每个盒子最多装limit个球,问共有多少方案?非常经典的数学题,解题思路是合法方案数 = 总的方案数 - 不合法的方案数,其中总的方案数很容易求,不合法的方案数可以用容斥原理求,两个的时间复杂度都为O(1)

1.总的方案数的求解

"隔板法" :将n个球放成一排,在他们中间插入两个隔板,将球分为三组,有多少种排列方式,注意两个隔板可以放在一起,因为题目允许盒子为空,根据排列组合,共有C(n+2,2)种方法

可能有人对这个公式的由来不理解,其实我们也可以用乘法原理来求解,我们先将一个隔板插入,有n+1个位置可以选,再将第二个隔板插入,这时第一个隔板也被看做是球,则有n+2个位置可以选,而两个隔板的放置可能出现第一个隔板的位置和上一次第二个隔板的位置相同,第二个隔板的位置和上一次第一个隔板的位置相同的情况,所以总方案数要除以2,为(n+1)*(n+2)/2=C(n+2,2)

2.不合法的方案数---容斥原理

集合A---A盒中球的数量超出limit的方案数

集合B---B盒中球的数量超出limit的方案数

集合C---C盒中球的数量超过limit的方案数

根据容斥原理:不合法的方案数=A+B+C-A∩B-A∩B-A∩B+A∩B∩C

而A=B=C,A∩B=A∩C=B∩C

所以我们只要求三个数就行

A:提前在A盒中放入limit+1个球,让剩下的球随便放,方案数为C(n-limit-1+2,2)

A∩B:提前在A盒、B盒中放入limit+1个球,让剩下的球随便放,方案数为C(n-2*(limit-1)+2,2)

A∩B∩C:提前在A盒、B盒、C盒中放入limit+1个球,让剩下的球随便放,方案数为C(n-3*(limit-1)+2,2)

注意:上面三个的前提是还有剩余的球,如果剩余的球<=0,则方案数为0

代码如下 

class Solution {
    typedef long long LL;
public:
    LL C2(LL n)//计算C(n,2)
    {
        if(n>1)
            return n*(n-1)/2;
        else
            return 0;
    }
    long long distributeCandies(int n, int limit) {
        return C2(n+2)-3*C2(n-(limit+1)+2)+3*C2(n-2*(limit+1)+2)-C2(n-3*(limit+1)+2);
    }
};

三、重新排序后包含指定子字符串的字符串的数目 

这题也能用容斥原理来做:(题目中是小写,下面用大写代替)

A集合:L没有出现的方案数                                                                                25^n

B集合:E没有出现或只出现一次的方案数                                                          25^n+n*25^(n-1)

C集合:T没有出现的方案数                                                                                25^n

A∩B:L没有出现 并且 E没有出现或只出现一次的方案数                                   24^n+n*24^(n-1)

A∩C:L没有出现 并且 T没有出现的方案数                                                         24^n

B∩C:T没有出现 并且 E没有出现或只出现一次的方案数                                   24^n+n*24^(n-1)

A∩B∩C:L没有出现 并且 T没有出现 并且 E没有出现或只出现一次的方案数    23^n+n*23^(n-1)

不合法方案数(容斥原理):A+B+C-A∩B-A∩B-A∩B+A∩B∩C

总的方案数:26^n

计算幂,要用到快速幂的算法,时间复杂度为O(logn) 

代码如下

class Solution {
    typedef long long LL;
    const int MOD=1e9+7;
public:
    //快速幂
    LL POW(LL a,LL b)//a^b
    {
        int res=1;
        while(b){
            if(b&1) res=(res*a)%MOD;
            a=(a*a)%MOD;
            b>>=1;
        }
        return res;
    }
    int stringCount(int n) {
        //公式可以化简如下
        LL ans=POW(26,n)-((75+n)*POW(25,n-1)-(72+2*n)*POW(24,n-1)+(23+n)*POW(23,n-1));
        return (ans%MOD+MOD)%MOD;
    }
};

当然如果你没想到或者没学过容斥原理,这题还能用分组背包来求解,问题转化为:每次在'a'-'z'中选择一个字母,要求最后选择了至少1个L,1个T,2个E的方案数,时间复杂度为O(n)

代码如下

class Solution {
    const int MOD=1e9+7;
public:
    int stringCount(int n) {
        long long memo[n+1][2][2][3];
        memset(memo,-1,sizeof(memo));
        function<int(int,int,int,int)>dfs=[&](int i,int L,int T,int E)->int{
            if(i==0)
                return L==0&&T==0&&E==0;
            if(memo[i][L][T][E]!=-1) return memo[i][L][T][E];
            long long res=0;
            res+=dfs(i-1,0,T,E);//选择L
            res+=dfs(i-1,L,0,E);//选择T
            res+=dfs(i-1,L,T,max(E-1,0));//选择E
            res+=(long long)dfs(i-1,L,T,E)*23;//选择其他的字母
            return memo[i][L][T][E]=res%MOD;
        };
        return dfs(n,1,1,2);
    }
};

四、购买物品的最大开销

 

这题反倒是没什么难点, 我们根据题意,就能猜到小天数*小的商品价格得到的乘积之和为最大开销(根据排序不等式可知),接下来,只要证明我们能在某天买到与之对应的小的商品价格,这很容易证明:因为我们每次选择都是在每一行剩余元素的最小值组成的集合中,即我们可以选到剩余所有数中的最小值。

代码如下

class Solution {
public:
    long long maxSpending(vector<vector<int>>& values) {
        int n=values.size(),m=values[0].size();
        vector<int>dp(m*n);
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                dp[i*m+j]=values[i][j];
            }
        }
        sort(dp.begin(),dp.end());
        long long ans=0;
        for(int i=0;i<m*n;i++){
            ans=(ans+(long long)(i+1)*dp[i]);
        }
        return ans;
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值