状态压缩DP UVa10817-Headmaster's Headache

本文介绍了如何运用状态压缩动态规划(DP)解决UVa10817-Headmaster's Headache问题。该题目涉及在确保每门课程至少有两位教师授课的情况下,最小化教师工资总支出。通过16位二进制串表示课程状态,低八位表示一位教师,高八位表示两位教师。状态转移方程用于计算考虑前n个教师时的最小开销。
摘要由CSDN通过智能技术生成

状态压缩是指使用计算机二进制位来存储状态,一般用法是将二进制串当做一个集合,bit位代表集合中的元素,bit位取值表示元素是否在集合中。n位二进制串可以描述2^n种集合(状态),因此对于n的取值是相当严格的。状态压缩DP中需要使用各种位运算来描述状态转换。所以需要对位运算的使用有一定的了解,下面是一些巧妙的运用。

  1. 把一个数字x最靠右的第一个1去掉 : x = x & (x-1)
  2. 判断数字x中是否存在相邻的1 : x & (x >> 1)

需要注意,位运算的优先级通常比较低,如果不熟悉这些优先级,建议多使用括号避免出错。

 


UVa10817-Headmaster's Headache是一道典型的状态压缩DP。

题意:
某校有某个教师和n个求职者,需讲授s个课程(1 ≤ s ≤ 8, 1 ≤ m ≤ 20, 1 ≤ n ≤ 100)。已知每人工资c(10000 ≤ c ≤ 50000)和能教的课程集合,要求支付最少的工资使得每门课至少有两个教师能教。在职教师不能辞退。

 

分析:
本题使用状态压缩显然应该讲课程作为要压缩的状态,考虑每门课程至少有两个教师教。状态定义如下:

  1. 16位长二进制串代表状态,bit位代表课程,其中低八位代表有一位老师教该课程,高八位代表有两个老师教该课程。
  2. dp[n][s]代表考虑前n个老师的情况下,维护一个状态最少需要多少开销。
  3. 每个老师维护一个状态,代表其能教导的课程。

此时状态转移方程如下:

int changeState(int from, int by)
{
    int first = (~from) & by;                       // 第一个上这门课的老师
    int second = from & by;                         // 第二个上这门课的老师
    int to = (from | first | (second << 8));        // 增加这个老师后的状态
    return to;
}

 

代码:
 

// 状态压缩DP
#include <iostream>
#include <sstream>
#include <cstdio>
#include <bitset>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_S = 8;
const int MAX_N = 100;
const int MAX_STATE = (1 << 16) - 1;
const int INF_ = 0x3f3f3f3f;

int begin_state;                                    // 初始状态
int begin_cost;                                     // 初始开销
int states[MAX_N+1];                                // 应聘者状态
int costs[MAX_N+1];                                 // 应聘者工资
int dp[MAX_N+1][MAX_STATE+1];                       // 考虑前i个应聘者时,维护每个状态所需最小开销

int changeState(int from, int by)
{
    int first = (~from) & by;                       // 第一个上这门课的老师
    int second = from & by;                         // 第二个上这门课的老师
    int to = (from | first | (second << 8));        // 增加这个老师后的状态
    return to;
}

void input(int s, int m, int n)
{
    begin_state = ((1 << 16) - 1) - ((1 << (s+8))-1) + ((1 << 8) - 1) - ((1 << s) - 1);
    begin_cost = 0;
    for (int i = 0; i < m; i++)
    {
        string str;
        int cost, lesson;
        cin >> cost;
        begin_cost += cost;
        getchar();
        getline(cin, str);
        stringstream ss(str);
        while (ss >> lesson)
        {
            lesson--;
            begin_state = changeState(begin_state, (1 << lesson));
        }
    }
    for (int i = 1; i <= n; i++)
    {
        string str;
        int state = 0, lesson;
        cin >> costs[i];
        getchar();
        getline(cin, str);
        stringstream ss(str);
        while (ss >> lesson)
        {
            lesson--;
            state = changeState(state, (1 << lesson));
        }
        states[i] = state;
    }
}

int solve(int s, int m, int n)
{
    memset(dp, INF_, sizeof(dp));
    dp[0][begin_state] = begin_cost;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= MAX_STATE; j++)
        {
            int to_state = changeState(j, states[i]);
            dp[i][j] = min(dp[i][j], dp[i-1][j]);                           // 不增加这个老师
            dp[i][to_state] = min(dp[i][to_state], dp[i-1][j]+costs[i]);    // 增加这个老师
        }
    }
    return dp[n][MAX_STATE];
}

int main()
{
    int s, m, n;
    while (true)
    {
        cin >> s >> m >> n;
        if (s == 0) break;
        input(s, m, n);
        cout << solve(s, m, n) << endl;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值