2020/5/4/ 每日一咕

11 篇文章 1 订阅
7 篇文章 0 订阅

2020/5/4 来两道状压dp

1.状压DP

1434. 每个人戴不同帽子的方案数 - 力扣(LeetCode)
总共有 n n n 个人和 40 40 40 种不同的帽子,帽子编号从 1 1 1 40 40 40

给你一个整数列表的列表 hats ,其中 hats[i] 是第 i 个人所有喜欢帽子的列表。

请你给每个人安排一顶他喜欢的帽子,确保每个人戴的帽子跟别人都不一样,并返回方案数。

由于答案可能很大,请返回它对 1 0 9 + 7 10^9 + 7 109+7 取余后的结果。

示例 1:
输入:hats = [[3,4],[4,5],[5]]
输出:1
解释:给定条件下只有一种方法选择帽子。
第一个人选择帽子 3,第二个人选择帽子 4,最后一个人选择帽子 5。

示例 2:
输入:hats = [[3,5,1],[3,5]]
输出:4
解释:总共有 4 种安排帽子的方法:
(3,5),(5,3),(1,3) 和 (1,5)

示例 3:
输入:hats = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
输出:24
解释:每个人都可以从编号为 1 到 4 的帽子中选。
(1,2,3,4) 4 个帽子的排列方案数为 24 。

示例 4:
输入:hats = [[1,2,3],[2,3,5,6],[1,3,7,9],[1,8,9],[2,5,7]]
输出:111

提示:
n == hats.length
1 <= n <= 10
1 <= hats[i].length <= 40
1 <= hats[i][j] <= 40
hats[i] 包含一个数字互不相同的整数列表。

40顶帽子压不了,压的是人。所以反过来一下,搞个human[i]对应第i顶帽子被那些人喜欢。
为了防止一顶帽子多人用,采取倒序遍历,同01背包。

class Solution {
    static const int mod = 1e9 + 7;

   public:
    int numberWays(vector<vector<int>>& hats) {
        vector<vector<int>> humans(41);
        for (int i = 0; i < hats.size(); ++i) {
            for (auto& it : hats[i]) {
                humans[it].emplace_back(i);
            }
        }
        vector<int> dp(1 << hats.size(), 0);
        dp[0] = 1;
        for (int i = 1; i < 41; ++i) {
            for (int j = (1 << hats.size()) - 1; j >= 0; --j) {
                for (auto& human : humans[i]) {
                    if (j & (1 << human)) continue;
                    dp[j | (1 << human)] += dp[j];
                    dp[j | (1 << human)] %= mod;
                }
            }
        }
        return dp.back();
    }
};

2.状压DP

Problem - 2180
遥远的瓦罗兰大陆上,有一座神秘的城池——班德尔城,班德尔城的历史非常悠久,其主要居民都是身高1m左右的约德尔人。而保卫这座城池安全的正是班德尔城最富盛名的特种部队之一“主舰斥候队”。提莫队长正是斥候队的侦查兵首领。凭借一手“致盲吹箭”和久负盛名的“小莫快跑”,提莫队长可攻可守,击退了无数试图进犯班德尔城的敌人。
但是众所周知,真正使他在瓦罗兰大陆上声名鹊起,令无数豪杰闻之而胆颤的是他的蘑菇。
但是因为这是一个算法题,而不是一场故事会。所以你需要帮助提莫队长解决一个困扰了他很久的问题。
给定一个长为n,宽为m的区域。这一片区域上由各种地形组成。其中包括
陆地,用大写字符“L”表示
水面,用大写字符“W”表示
森林,用大写字符“F”表示
城池,用大写字符“C”表示
提莫队长需要在这片区域上布置他的蘑菇来抵御随时可能进犯的敌人。
为了更好的隐蔽他的蘑菇只能被种在森林区域。
并且因为蘑菇存在可能爆炸的风险,所以不能够将一个蘑菇种在另一个蘑菇的爆炸范围内。
同时为了保证城池的安全,每座城池也均不能在蘑菇的爆炸范围内
每一个蘑菇的攻击范围包含其上下左右四个方向和自身共计9个单位区域内。
在这里插入图片描述在这里插入图片描述

如图1这样一个区域内,提莫只能在F内种下蘑菇
如图2在(2,3)种下一个蘑菇,红色区域即为蘑菇的攻击范围
现在请你帮助提莫队长安排一个最优方案以便在这片区域内能够种下最多的蘑菇,来抵御敌人的入侵,守护班德尔城的安宁

第一行两个整数n,m 1<=n<=100,1<=m<=10
接下来n行为区域的分布,保证不含有除“LWFC”外的任何字符

输入
5 4
FLFF
FFLL
FFFF
FLFF
FLLF
输出
6

因为只有10列,同样考虑状压dp。因为炸弹距离是2,所以同一行内放置的炸弹的距离不能 ≤ \le 2。所以看似1<<10,实则测了一下极限情况只有60种。所以剪好枝是可以三重1<<10的。
接下来设dp[i][j][k]为第i行放置情况为j,i-1行放置为k的最大炸弹数。
d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ k ] [ l ] + c n t ( j ) ) 其 中 l 为 i − 2 行 的 放 置 情 况 , c n t ( j ) 为 j 的 炸 弹 数 。 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+cnt(j))其中l为i-2行的放置情况,cnt(j)为j的炸弹数。 dp[i][j][k]=max(dp[i][j][k],dp[i1][k][l]+cnt(j))li2cnt(j)j
因为空间限制,第一维可以滚动一下,只开3甚至2就足够了,因为只和前两行有关。
第一行和第二行要特殊处理,因为第一行上一行是零行,无限制所以是0。
记得这里要跟着更新ans。
城池’C’的预先处理周围的’F’为其他即可,不是’F’的不要改,否则可能改到另一个’C’
同一行的限制是放置的炸弹距离不能 ≤ \le 2,以及放的地方要是’F’。
不同行的限制是上面两行炸弹不能放在同一列。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int maxn = 3e5 + 5;
const ll inf = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
#define all(x) x.begin(), x.end()
char mp[105][15];
int dp[3][1 << 10][1 << 10];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        cin >> mp[i];
    }
    int ways[][2] = {{0, -2}, {0, -1}, {0, 1}, {0, 2},
                     {-2, 0}, {-1, 0}, {1, 0}, {2, 0}};
    auto lmt_ck = [&](int i, int j) {
        return 0 <= i && i < n && 0 <= j & j < m;
    };
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (mp[i][j] == 'C') {
                for (auto &it : ways) {
                    int I = i + it[0], J = j + it[1];
                    if (lmt_ck(I, J) && mp[I][J] == 'F') {
                        mp[I][J] = 'W';
                    }
                }
            }
        }
    }
    auto ck = [&](int i, int states) {
        for (int j = 0, last = -10; j < m; ++j) {
            if ((1 << j) & states) {
                if (i >= 0 && mp[i][j] != 'F') return false;
                if (j - last <= 2) return false;
                last = j;
            }
        }
        return true;
    };
    int cnt = 0;
    int ans = 0;
    for (int j = 0; j < (1 << m); ++j) {
        if (ck(0, j)) {
            dp[0][j][0] = __builtin_popcount(j);
            ans = max(ans, dp[0][j][0]);
        }
    }
    for (int j = 0; j < (1 << m); ++j) {
        if (ck(1, j)) {
            for (int k = 0; k < (1 << m); ++k) {
                if ((j & k) == 0 && ck(0, k)) {
                    dp[1][j][k] = dp[0][k][0] + __builtin_popcount(j);
                    ans = max(ans, dp[1][j][k]);
                }
            }
        }
    }

    for (int i = 2; i < n; ++i) {
        for (int j = 0; j < (1 << m); ++j) {
            if (ck(i, j)) {
                for (int k = 0; k < (1 << m); ++k) {
                    if ((j & k) == 0 && ck(i - 1, k)) {
                        for (int l = 0; l < (1 << m); ++l) {
                            if ((j & l) == 0 && (k & l) == 0 && ck(i - 2, l))
                                dp[i % 3][j][k] = max(
                                    dp[i % 3][j][k], dp[(i + 2) % 3][k][l] +
                                                         __builtin_popcount(j));
                        }
                    }
                    if (i == n - 1) {
                        ans = max(ans, dp[i % 3][j][k]);
                    }
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值