HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-10-03 10:32阅读: 41评论: 0推荐: 0

状压DP

集合状压DP

Doing Homework HDU - 1074

题意:

1:每次给n个作业及其花费时间和截至时间
2:每次做一个作业直到做完才可以换下一个作业
3:对于每一个作业,每超过一天就会减去1分
4:问减去的最小的分数
5:题中的输入顺序是按字典序的

思路:

状态压缩动态规划
在二进制情况下,第 i 位为 1 表示已做完 为 0 表示未做
这样可以用最多 215 的数来表示所有情况

过程:
dp[i] 表示当前处于 i 状态,循环所有的状态:

  • 每次循环判断其他科目是否已完成,
    如果未完成,将现在状态加上完成的状态记录为新状态。

注意:
因为需要输出路径,所以dp要记录上一个节点:pre
因为要计算答案,所以dp要记录完成这些科目需要多长时间:time
因为要统计数量,所以dp要记录罚时:num

所有科目均为 1 就是答案,也就是dp[2n1]

由于输出是逆序,需要用栈等结构倒过来输出

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1<<16;
// #define int long long
int n;
struct node{
string name;
int time;
int en;
}a[20];
struct DP{
int num;
int day;
int pre;
}dp[N];
int GetChange(int num1,int num2) {
int n = num1 ^ num2;
int i = 0;
while(n) {
if(n & 1)
return i;
n >>= 1;
i++;
}
return 0;
}
void solve(){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i].name>>a[i].en>>a[i].time;
}
memset(dp,0x3f,sizeof dp);
dp[0].num = dp[0].day = dp[0].pre =0;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if((1<<j) & i ) continue;
int ne=i|(1<<j);
int cnt=max(0 , dp[i].day+a[j].time-a[j].en);
// cout<<i<<" "<<j<<" " <<ne<<" "<<cnt<<" "<<"XxX"<<endl;
if(dp[ne].num>dp[i].num+cnt){
dp[ne].num=dp[i].num+cnt;
dp[ne].day=dp[i].day+a[j].time;
dp[ne].pre=i;
}
}
}
int k = (1 << n) - 1;
cout<<dp[k].num<<endl;
stack<int > stk;
while(k){
int pos = GetChange(k,dp[k].pre);
stk.push(pos);
k = dp[k].pre;
}
while(!stk.empty()) {
cout << a[stk.top()].name << endl;
stk.pop();
}
}
signed main()
{
cin.tie(0);
std::cin.sync_with_stdio(false);
int t;cin>>t;
while(t--) solve();
return 0;
}

棋盘状压DP

原文
AcWing 1064. 小国王【线性状压DP+滚动数组优化+目标状态优化】

题目描述

n×n(1n10) 的棋盘上放 k(0kn2) 个国王

国王可攻击相邻的 88 个格子,求使它们 无法互相攻击 的 方案总数

八相邻:
通常意义上的八相邻指的是当前元素的上、下、左、右、左上、右上、左下、右下八个方向

分析

这种 棋盘放置类 问题,在没有事先知道一些特定 性质 的情况下来做,都会想到 爆搜

本题的数据规模,也是向着 爆搜 去设置的

如果我们直接 爆搜,则 时间复杂度 为 O(2n2) 是会超时的,因此会想到用 记忆化搜索 来进行优化

考虑一下如何进行 动态规划

由于在第 i 层放置国王的行为,受到 i1 层和 i+1 层以及 i 层的状态影响

那么我们就可以规定从上往下枚举的顺序,这样考虑第 i 层状态时,只需考虑 i1 层的状态即可

于是乎我们可以考虑把层数 i 作为动态规划的 阶段 进行 线性DP

而第 i 阶段需要记录的就是前 i 层放置了的国王数量 j,以及在第 i 层的 棋盘状态 k

然后按照题意找到,哪些 棋盘状态 是合法的,哪些 棋盘状态的转移 是合法的即可

image

代码

#include <iostream>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << N, C = N * N;
int n, m, K;
LL f[N][C][M];
int cnt[M];
vector<int> legal_state;
vector<int> state_trans[M];
bool check(int state)
{
return !(state & state >> 1);
}
int count(int state)
{
int res = 0;
for (int i = 0; i < n; ++ i) res += state >> i & 1;
return res;
}
int main()
{
cin >> n >> K;
//预处理所有合法状态
for (int st = 0; st < 1 << n; ++ st)
//检查当前状态是否合法
if (check(st))
legal_state.push_back(st),
cnt[st] = count(st);
m = legal_state.size();
//预处理所有合法状态的合法转移
for (auto cur_st: legal_state)
for (auto to_st: legal_state)
if (!(cur_st & to_st) && check(cur_st | to_st))//上下不相邻且纵坐标也不相邻
state_trans[cur_st].push_back(to_st);
//动态规划
f[0][0][0] = 1;
for (int i = 1; i <= n + 1; ++ i)
for (int j = 0; j <= K; ++ j)
for (auto &state: legal_state)
for (auto &pre_st: state_trans[state])
if (j - cnt[state] >= 0)
f[i][j][state] += f[i - 1][j - cnt[state]][pre_st];
//统计目标状态的所有方案数
cout << f[n + 1][K][0] << endl;
return 0;
}

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16750127.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(41)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起