状态压缩dp系列问题总结

1、Beautiful Arrangement

题目:

Suppose you have n integers labeled 1 through n. A permutation of those n integers perm (1-indexed) is considered a beautiful arrangement if for every i (1 <= i <= n), either of the following is true:

  • perm[i] is divisible by i.
  • i is divisible by perm[i].

Given an integer n, return the number of the beautiful arrangements that you can construct.

Example 1:

Input: n = 2
Output: 2
Explanation: 
The first beautiful arrangement is [1,2]:
    - perm[1] = 1 is divisible by i = 1
    - perm[2] = 2 is divisible by i = 2
The second beautiful arrangement is [2,1]:
    - perm[1] = 2 is divisible by i = 1
    - i = 2 is divisible by perm[2] = 1

Example 2:

Input: n = 1
Output: 1

Constraints:

  • 1 <= n <= 15

代码:

class Solution {
public:
    int countArrangement(int n) {
        vector<int> dp(1<<n,0);
        for(int i=0;i<n;i++){
            dp[1<<i]=1;    
        }
        for(int i=0;i<(1<<n);i++){
            long int index=1;
            int temp=i;
            while(temp){
                temp=temp&(temp-1);
                index+=1;
            }
            for(int j=0;j<n;j++){
                //二进制位为0(未被选取),且能放在第Index位
                if(!(i&(1<<j))&&((j+1)%index==0||index%(j+1)==0)){
                    dp[1<<j|i]+=dp[i];
                }
            }
        }
        return dp[(1<<n)-1];
    }
};

想法:用一个n位的二进制数表示二进制中为1的数字已任意顺序放在数组的前m位(m为该二进制数中1的个数)。

哈哈哈

2、参加考试的最大学生数

题目:

给你一个 m * n 的矩阵 seats 表示教室中的座位分布。如果座位是坏的(不可用),就用 '#' 表示;否则,用 '.' 表示。

学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的一起参加考试且无法作弊的最大学生人数。

学生必须坐在状况良好的座位上。

示例 1:

输入:seats = [["#",".","#","#",".","#"],
              [".","#","#","#","#","."],
              ["#",".","#","#",".","#"]]
输出:4
解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。 
示例 2:

输入:seats = [[".","#"],
              ["#","#"],
              ["#","."],
              ["#","#"],
              [".","#"]]
输出:3
解释:让所有学生坐在可用的座位上。
示例 3:

输入:seats = [["#",".",".",".","#"],
              [".","#",".","#","."],
              [".",".","#",".","."],
              [".","#",".","#","."],
              ["#",".",".",".","#"]]
输出:10
解释:让学生坐在第 1、3 和 5 列的可用座位上。

提示:

seats 只包含字符 '.' 和'#'
m == seats.length
n == seats[i].length
1 <= m <= 8
1 <= n <= 8

代码:

class Solution {
public:
    int maxStudents(vector<vector<char>>& seats) {
        int m=seats.size();
    int n=seats[0].size();
    vector<vector<int>> dp(m+1,vector<int>(1<<n)); //初始化

    for(int row=1;row<=m;row++){
        for(int s=0;s<(1<<n);s++){
            bitset<8> bs(s);
            bool ok=true;
            for(int j=0;j<n;j++){
                if((bs[j]&&seats[row-1][j]=='#')||(j<n-1&&bs[j]&&bs[j+1])){
                    ok=false;
                    break;
                }
            }
            if(!ok){
                dp[row][s]=-1;
                continue;
            }
            for(int last=0;last<(1<<n);last++) {
                if (dp[row - 1][last] == -1)continue;
                bitset<8> lbs(last);
                bool flag = true;
                for (int j = 0; j < n; j++) {
                    if (lbs[j] && ((j > 0 && bs[j - 1] || j < n - 1 && bs[j + 1]))) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    //flag为真说明这个last状态的每个位置都合法
                    dp[row][s] = max(dp[row][s], dp[row - 1][last] + (int) bs.count()); //转移方程
                }
            }
        }
    }
    int res=0;
    for(int i=0;i<(1<<n);i++){
        if(dp[m][i]>res)res=dp[m][i];
    }
    return res;
    }
};

想法:

每一行,遍历所有状态,判断且留下可行的状态(作为不是#,且左右没有相邻的1-座位),对于每个可行的状态,遍历上一行的所有状态,如果是-1,continue,

如果和这一行有冲突,构成左上左下关系,那么break,否则,使用状态转移方程:

最后,在最后一行的时候,遍历选择,最大的dp[row][s]值,得到条件下最多的人数。

3、Leetcode 638.Shopping Offers

题目:

在LeetCode商店中, 有许多在售的物品。

然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。

现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。

每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。

任意大礼包可无限次购买。

示例 1:

输入: [2,5], [[3,0,5],[1,2,10]], [3,2]
输出: 14
解释: 
有A和B两种物品,价格分别为¥2和¥5。
大礼包1,你可以以¥5的价格购买3A和0B。
大礼包2, 你可以以¥10的价格购买1A和2B。
你需要购买3个A和2个B, 所以你付了¥10购买了1A和2B(大礼包2),以及¥4购买2A。
示例 2:

输入: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
输出: 11
解释: 
A,B,C的价格分别为¥2,¥3,¥4.
你可以用¥4购买1A和1B,也可以用¥9购买2A,2B和1C。
你需要买1A,2B和1C,所以你付了¥4买了1A和1B(大礼包1),以及¥3购买1B, ¥4购买1C。
你不可以购买超出待购清单的物品,尽管购买大礼包2更加便宜。
说明:

最多6种物品, 100种大礼包。
每种物品,你最多只需要购买6个。
你不可以购买超出待购清单的物品,即使更便宜。

代码:

class Solution {
public:
    int dp[1000001];
    int mx = 1000000;
    int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        int ans = 0;
        for(int i = 0; i < needs.size(); i++) {
            ans += needs[i] << (i * 3);
        }
        mx = ans;
        memset(dp, 0x3f, sizeof(dp));
        dp[0] = 0;
        for(int i = 0; i < mx; i++) {
            if(dp[i] == 0x3f3f3f3f) continue;
            for(int j = 0; j < price.size(); j++) {
                int x = i + (1 << (j * 3));
                if(((x >> (j * 3)) & 7) > needs[j]) continue;
                if(x > mx) continue;
                dp[x] = min(dp[x], dp[i] + price[j]);
            }
            for(int j = 0; j < special.size(); j++) {
                int x = i, k;
                for(k = 0; k < special[j].size() - 1; k++) {
                    if(((x >> (k * 3)) & 7) + (special[j][k]) > needs[k]) {
                        x = mx + 1;
                        break;
                    }
                    x += special[j][k] << (k * 3);
                }
                
                if(x > mx) continue;
                //printf("%2o %2o %3d\n", i, x, j);
                dp[x] = min(dp[x], dp[i] + special[j][k]);
            }
        }
        return dp[ans];
    }
};

思路:

每个商品不超过6个,可以用三位二进制表示商品数目。  这样可以用一连串二进制数来表示用了多少个商品的状态。根据状态建立一个动态规划数组,对每个状态,遍历每个price,更新状态,如果状态[j]大于needs[j],或者状态大于最大状态,跳过,

dp[x]=min(dp[x],dp[i]+price[j]);遍历每个special大礼包,对于每个大礼包,如果加上之后该位超过needs[k],或者整个大礼包之后的状态大于最大状态,那么跳过。更新dp[x]=min(dp[x],dp[i]+special[j][k]);

最后得到的dp[ans]即是确切完成待购清单的最低花费。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值