[集合DP] UVa10817 校长的烦恼(多阶段决策)

题目

这里写图片描述

思路

依然是集合DP的基本思路,此处的集合是课程的集合。
用s0表示没人教的课程集合,s1表示恰好有一个人教的课程集合,s2表示有至少两个人教的课程集合。由于s0可以由s1和s2算出来,所以不用记录在状态里。(虽然状态很复杂,但能简单一些是一些
所以用d(i,s1,s2)表示前i个人,在s1,s2情况下的最小花费。此处应该注意的是,子集二进制表示法要求元素由0开始,题目给的是1,转化一下即可。


1.状态定义:d(i,s1,s2),在s1,s2情况下已经考虑了前i个人的最小花费。
2.初状态: d ( n , 0 , ( 1 << s ) 1 ) = 0 ,   d ( n , 0 , i ) = I N F   |   i   ( 1 << s ) 1 ) = 0
3.答案:d(0,0,0)。
(添加了s0的dp函数中,答案的参数为dp(0,(1 << s)-1,0,0))
4.状态转移方程:

d ( i , s 1 , s 2 ) = m i n { d ( i + 1 , s 1 , s 2 ) + p a y [ i ] ,   d ( i + 1 , s 1 , s 2 ) }

(第一项表示选这个人,第二项表示不选)
5.复杂度分析: 2 n (大概我瞎猜的)


关于输入,本题的输入是行无限的:

2 2 2
10000 1
20000 2
30000 1 2
40000 1 2
0 0 0

之前一直不会这种输入的解决办法,本题从LRJ那学到了:

int x;
  string line;
  while(getline(cin, line)) {
    stringstream ss(line);
    ss >> s >> m >> n;
    if(s == 0) break;

    for(int i = 0; i < m+n; i++) {
      getline(cin, line);
      stringstream ss(line);
      ss >> c[i];
      st[i] = 0;
      while(ss >> x) st[i] |= (1 << (x-1));
}



关于本题中复杂的二进制子集操作,应该掌握:

int m0 = st[i] & s0, m1 = st[i] & s1; 
// m0,教师能教的但还没人教的课程。m1,教师能教的但有一个人教了的课程。
s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
ans = min(ans, pay[i] + dp(i + 1, s0, s1, s2)); 

给人感觉子集擦做里,^就是-,|就是+,&就是找交集。

代码

(UVA崩了,所以本代码并没有拿去评测)

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <sstream>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int INF = 1000000;
const int maxn = 28 + 2;
const int maxs = 8+1;
int s, m, n, d[maxn][1<<maxs][1<<maxs], pay[maxn], st[1<<maxs];
// st[i]表示,序号为i的教师能教授的课程集合,此处直接用了集合。

int dp(int i, int s0, int s1, int s2) {
    if (i == m + n)
        return s2 == (1 << s) - 1 ? 0 : INF;
    int &ans = d[i][s1][s2];
    if (ans != -1) return ans;
    ans = INF;
    if (i >= m) ans = dp(i + 1, s0, s1, s2);  // 不选
    int m0 = st[i] & s0, m1 = st[i] & s1; // m0,教师能教的但还没人教的课程。m1,教师能教的但有一个人教了的课程。
    s0 ^= m0; s1 = (s1 ^ m1) | m0; s2 |= m1;
    ans = min(ans, pay[i] + dp(i + 1, s0, s1, s2));  // 选
    return ans;
}

int main() {
    while (scanf("%d%d%d", &s, &m, &n) == 3 && s) {
        string line;
        getline(cin, line);
        _for(i, 0, m+n) {
            getline(cin, line);
            stringstream ss(line);
            ss >> pay[i];
            st[i] = 0;
            int x;
            while (ss >> x) st[i] |= (1 << (x - 1));
            // 为了表示方便,子集表示时往往从0开头。
            // 此处的|=为添加元素或子集
        }
        memset(d, -1, sizeof(d));
        printf("%d\n", dp(0, (1 << s) - 1, 0, 0));
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80995941