【算法竞赛入门经典】集合的动态规划;位运算 例题9-15 UVa10817

【算法竞赛入门经典】集合的动态规划;位运算 例题9-15 UVa10817

例题UVa1210817

The headmaster of Spring Field School is considering employing some new teachers for certain subjects. There are a number of teachers applying for the posts. Each teacher is able to teach one or more subjects. The headmaster wants to select applicants so that each subject is taught by at least two teachers, and the overall cost is minimized.

Input

The input consists of several test cases.
The format of each of them is explained below:
The first line contains three positive integers S, M and N. S (≤ 8) is the number of subjects, M( ≤ 20) is the number of serving teachers, and N (≤ 100) is the number of applicants.
Each of the following M lines describes a serving teacher. It first gives the cost of employing him/her (10000 ≤ C ≤ 50000), followed by a list of subjects that he/she can teach. The subjects are numbered from 1 to S. You must keep on employing all of them. After that there are N lines, giving the details of the applicants in the same format.
Input is terminated by a null case where S = 0. This case should not be processed.

Output

For each test case, give the minimum cost to employ the teachers under the constraints.

Sample Input

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

Sample Output

60000

分析

平心而论,这道题的状态转移不是非常友好,理解起来很不舒服。
大概有点类似于背包问题?第i个老师选不选的问题,但是状态转移不是一下就能做好的东西。
方便起见,用s1,s2表示有一个人教授的科目和两个人及以上的人教授的科目。
以前固定思维的是,d[i]表示的是前i的花费,然后d[i]根据以前刷表填好的数据来填,但是这题目不能这么做,原因:
那我们假设d[i][s1][s2]是前i个人考虑完之后,状态为s1,s2时的总耗费,发现计算d[i][s1][s2]难以进行状态转移,因为你不知道d[i][s1][s2]是从d[i-1][X][X]哪种状态变来的。
因此我们回头审视一下这个过程,发现你选择完第i人之后,第i+1个人需要面临的状态就确定了,因此,不妨从前往后计算。那么换一种表示方式,d[i][s1][s2]就变成了,第i个人面对的状态是s1,s2时,第i个人到最后一个人的总花费
这样的目的时,确定第i个人的状态之后,能够轻松的确定第i+1个人面对的状态,从而能够轻易的进行状态转移。
这里学到了:状态转移并不一定总是被动的自己去求上一个状态在哪,也有可能是求出状态供给下一个用并转移到下一个那边,这在集合的动态规划里是常见的
那么,d[i][s1][s2]的转移就成为了:
1.不选择第i个人(只有i>m时才允许不选),此时第i+1个人和第i个人面对的状态相同
ans=dp(i+1,s0,s1,s2);
2.选择第i个人,此时,根据计算可得到新的s0,s1,s2即第i+1个人面对的状况。
把这个状态跟INF或者不选择第i个人时的值比较,求小的那个

int m0=able[i]&s0,m1=able[i]&s1;
    s0^=m0;
    s1=(s1^m1)|m0;
    s2|=m1;
    ans=min(ans,dp(i+1,s0,s1,s2)+cost[i]);

样例实现代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<sstream>
#define maxs 9
#define maxn 120+5
#define INF 1000000000
using namespace std;
int s,m,n;
int cost[maxn],able[maxn],d[maxn][1<<maxs][1<<maxs];
int dp(int i,int s0,int s1,int s2){
    if(i==m+n+1){
        if(s2==(1<<s)-1)
            return 0;
        else
            return INF;
    }
    int &ans=d[i][s1][s2];
    if(ans>=0)
        return ans;
    ans=INF;
    if(i>m){
        ans=dp(i+1,s0,s1,s2);
    }
    int m0=able[i]&s0,m1=able[i]&s1;
    s0^=m0;
    s1=(s1^m1)|m0;
    s2|=m1;
    ans=min(ans,dp(i+1,s0,s1,s2)+cost[i]);
    return ans;
}
int main(){
    int temp;
    while(cin>>s>>m>>n&&s){
        getchar();
        memset(able,0,sizeof(able));
        string st;
        for(int i=1;i<=m+n;i++){
            getline(cin,st);
            stringstream ss(st);
            ss>>cost[i];
            while(ss>>temp){
                able[i]|=(1<<(temp-1));
            }
        }
        memset(d,-1,sizeof(d));
        int ans=dp(1,(1<<s)-1,0,0);
        cout<<ans<<endl;
    }
    return 0;
}

结果

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值