UVA 10817 UVALive 4643 状压dp

本文解析了两道典型的动态规划题目,一是如何以最低成本安排足够教师教授所有课程,二是如何通过最少的问题区分所有二进制串。通过具体实例和代码展示了动态规划算法的设计与实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两个题的思想是相似的,可以对比一下。
题目: UVA - 10817 Headmaster’s Headache
题意:

某校有n个教师和m个求职者,已知每人的工资和能教的课程集合,要求支付最少的工资使得每门课都至少有两名教师教学。在职教师必须招聘。

分析:

d[i][s1][s2]表示考虑到第i个教师以后,可以教授的课程集合是s1和s2,还需要花费的钱?其中s1和s2分别保存某门课程是否有一个老师教。因为要求每门课至少两名老师,所以维护两个集合,如果两个集合每门课都有老师教,那么就不需要花钱招聘其他老师了。
维护s1和s2的时候,先填s1再填s2

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 9;
const int N = (1 << 8) + 9;
int d[130][N][N], s[130], w[130];
bool vis[130][N][N];
int ALL, n, S, m;;
int dp (int i, int s1, int s2) {
    if (i == m + n) return s1 == ALL && s2 == ALL ? 0 : INF;
    if (vis[i][s1][s2]) return d[i][s1][s2];
    vis[i][s1][s2] = 1;
    int& ans = d[i][s1][s2];
    ans = INF;
    if (i >= m) ans = dp (i + 1, s1, s2);
    for (int k = 0; k < S ; k++)
        if (s[i] & (1 << k) ) {
            if (! (s1 & (1 << k) ) ) s1 |= (1 << k);
            else if (! (s2 & (1 << k) ) ) s2 |= (1 << k);
        }
    ans = min (ans, w[i] + dp (i + 1, s1, s2) );
    return ans;
}

char ch;
int main() {
    //freopen ("f.txt", "r", stdin);

    while (~scanf ("%d%d%d", &S, &m, &n) && (S + m + n) ) {
        memset (vis, 0, sizeof (vis) );
        for (int i = 0; i < m; i++) {
            scanf ("%d", &w[i]);
            s[i] = 0;
            while (ch = getchar() ) {
                if (ch == '\n') break;
                if (ch >= '1' && ch <= '8') s[i] |= (1 << (ch - '1') );
            }
        }
        for (int i = m; i < n + m; i++) {
            scanf ("%d", &w[i]);
            s[i] = 0;
            while (ch = getchar() ) {
                if (ch == '\n') break;
                if (ch >= '1' && ch <= '8') s[i] |= (1 << (ch - '1') );
            }
        }
        ALL = (1 << S) - 1;
        printf ("%d\n", dp (0, 0, 0) );
    }
    return 0;
}
/*
SampleInput
2 2 2
10000 1
20000 2
30000 1 2
40000 1 2
0 0 0
SampleOutput
6000

*/

题目: UVALive - 4643 Twenty Questions
题意:

有n个长度为m的二进制串,每个都是不同的。
为了把所有字符串区分开,你可以询问,每次可以问某位上是0还是1。
问最少提问次数,可以把所有字符串区分开来。

分析:

这题自己没做出来QAQ,看了题解瞬间明白了~~
f[s1][s2]: 表示提问的问题是{s1}集合,答案是{s2}时,还需要问几次才可以全部区分开,当问题集合为{s1}时, 如果还不能区分所有答案,那么就需要继续再问一个问题,那么可以推出下一个问题的集合为:
nextQuestions = { s1 | (1<< k), 当s1的k位上为0的时候 }
那么可以得到:
f[s1][s2] = 0, 如果和答案s2相同的个数小于等于1,那么已经可以全部区分开了,还要询问0次
f[s1][s2] = { min(f[nextQuestions][s2], f[nextQuestions][s2^(1<< k)]), 当s1的k位上为0时} //相应答案是0或者是1

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 9;
const int N = (1 << 11) + 9;
int f[N][N], vis[N][N], p[200], n, m;
int dp (int s1, int s2) {
    if (vis[s1][s2]) return f[s1][s2];
    vis[s1][s2] = 1;
    int& ans = f[s1][s2];
    int cnt = 0;
    for (int i = 0; i < n; i++)
        if ( (s1 & p[i]) == s2) cnt++;
    if (cnt <= 1) return ans=0;

    ans = INF;
    for (int i = 0; i < m; i++) {
        if ( (s1 & (1 << i) ) ) continue;
        ans = min (ans, max (dp (s1 | (1 << i), s2 | (1 << i) ), dp (s1 | (1 << i), s2) ) + 1);
    }
    return ans;
}
int main() {
    //freopen ("f.txt", "r", stdin);

    while (~scanf ("%d%d", &m, &n) && (n + m) ) {

        memset (vis, 0, sizeof (vis) );
        int a;
        for (int i = 0; i < n; i++) {
            p[i] = 0;
            for (int j = 0; j < m; j++) {
                scanf ("%1d", &a);
                if (a) p[i] |= (1 << j);
            }
        }

        printf ("%d\n", dp (0, 0) );

    }
    return 0;
}
/*
Sample Input

8 1
11010101
11 4
00111001100
01001101011
01010000011
01100110001
0 0

Sample Output

0
2

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值