状压DP【Disease Manangement 疾病管理】题解
这个题是一个状压,应该能看出来吧
题目描述
有N头牛,它们可能患有D种病,现在从这些牛中选出若干头来,但选出来的牛患病的集合中不过超过K种病.
Alas! A set of D (1 <= D <= 15) diseases (numbered 1..D) is running through the farm. Farmer John would like to milk as many of his N (1 <= N <= 1,000) cows as possible. If the milked cows carry more than K (1 <= K <= D) different diseases among them, then the milk will be too contaminated and will have to be discarded in its entirety. Please help determine the largest number of cows FJ can milk without having to discard the milk.
输入格式
Line 1: Three space-separated integers: N, D, and K
Lines 2..N+1: Line i+1 describes the diseases of cow i with a list of 1 or more space-separated integers. The first integer, d_i, is the count of cow i"s diseases; the next d_i integers enumerate the actual diseases. Of course, the list is empty if d_i is 0.
输出格式
Line 1: M, the maximum number of cows which can be milked.
样例
样例输入
6 3 2
0---------第一头牛患0种病
1 1------第二头牛患一种病,为第一种病.
1 2
1 3
2 2 1
2 2 1
样例输出
5
直接看代码罢,懒得打字,思路甚么的看代码注释罢。。
//首先明确我们的状态指的是什么:{0 1 0 1 0 0}表示的是当前选出来的牛牛一共患了第3,5两种病
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<queue>
#define ll long long
#define re register int
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define sandom signed
#define disease 17
#define cownum 1005
using namespace std;
int n,d,k;//n头牛,d种病,最多只能有k种病
int ill[cownum],num[1<<15];//num[s]是表示s状态有多少病,ill[cow]表示第cow头牛都有啥病,用二进制表示
int f[1<<15];//f[s]表示s状态下能选牛的最大数目
/*
6 3 2
0
1 1
1 2
1 3
2 2 1
2 2 1
*/
inline int read(){//快读(。)
int x = 0;char c;bool f = false;
while(!isdigit(c = getchar())){
if(c == '-'){
c = getchar(),f = true;
break;
}
}
do{
x = (x << 3) + (x << 1) + (c & 15);
}while(isdigit(c = getchar()));
if(f == true) return -x;
return x;
}
void get_numberone_in_every_zhuangtai(){
//先循环每种状态
for(re s = 1 ; s <= (1 << d) ; ++ s){//从0开始是为了{0 0 0 0}也算一种状态,不过,當,當然了,从0开始枚举也没啥用,num[0]本来就是0...
for(re illness = 1 ; illness <= d ; ++ illness){//循环每种病
if( (s & (1 << (illness-1))) != 0){//如果这一种病表示的状态和状态s有交叉,就说明这个状态中牛患了这种病
++ num[s];
/*
至于为什么是减一,我想可能有oier不明白,犇犇可以跳过这个注释
看这个例子:
we想用{0 0 1 0}来表示犇犇(划掉)牛患了第2种病
所以此时illness循环到的是2,没异议吧
那么转换二进制的时候如果不减1:(1 << 2) = {0 1 0 0} 这么着跟我们想要的不一样
所以要减去1 (1 << (2-1)) = (1 << 1) = {0 0 1 0}
*/
}
}
}
}
void work(){
n = read(),d = read(),k = read();
get_numberone_in_every_zhuangtai();
for(re i = 1,number,tmp ; i <= n ; ++ i){
number = read();
while(number --){
tmp = read();
ill[i] |= (1 << (tmp-1));//这个是把每个牛得的病用二进制状态存起来
}
}
for(re s = ((1 << d) - 1) ; s >= 0 ; s --){//枚举每一个状态
for(re cow = 1 ; cow <= n ; ++ cow){//枚举每一头牛
f[(s | ill[cow])] = max( f[(s | ill[cow])] , f[s] + 1 );//一个dp方程,,,
/*关于为什么要倒着枚举:
我们的dp方程是f[s|ill[cow]] = max(f[s|ill[cow]],f[s]+1);
f[s|ill[cow]]指的是在s的基础上再选上cow这头牛一共能有几头,f[s]+1是没选这头牛的时候一共能有几头
s[|ill[cow]]肯定是:相对于枚举的状态s,其中的'1'只会增或者不变而不会减,如果你不明白为啥只增或者不变而不减,请移步代码的最后面的举例
如果说我们正着枚举,会有重复,但是很难用语言说清楚
重复指的是可能重复累计某一头牛的贡献
我来举个栗子:
ill[cow]:
{0 0 1 0}
s: {0 1 0 1}
|了之后:
{0 1 1 1}
这时候f[s|ill[cow]]会被更新一次,也就是会f[s]+1
但是,如果我们是正着枚举,循环几次就会该到了枚举这个状态s:
{0 1 1 1}
如果我们再去做运算,s|ill[cow]得到的结果还是{0 1 1 1},然后取max肯定是取f[s]+1,就又重复计算了cow这头牛,相当于又选了他一回
所以我们要避免这种情况的出现,也就是避免:枚举了{0 1 0 1}更新了{0 1 1 1},过了一会又枚举了{0 1 1 1}重复更新了{0 1 1 1}
还得是自己理解,真的很难用语言描述出来。。。
*/
// if((ill[cow]|s)<=s) f[s]++;//这么着写也可以
}
}
int final_ans = -9999;
// cout<<k<<endl;
for(re s = (1<<d)-1 ; s > 0 ; s --){
if(num[s] <= k){//别写成==k,这个东西是判断当前选出的牛牛得病的种类数是否合法
// cout<<f[s]<<" "<<s<<" "<<num[s]<<endl;
final_ans = max(final_ans,f[s]);
}
}
printf("%d",final_ans);
}
sandom main(){
work();
return 0;
}
/*关于为什么s|ill[cow]只增或者不变而不减,例子:
枚举的状态s:
{0 0 1 0 1 0}
ill[cow]:
{0 0 1 0 0 0}
或运算后得到:
{0 0 1 0 1 0}这个是不变的情况
枚举的状态s:
{0 0 1 0 1 0}
ill[cow]:
{0 0 0 1 0 0}
或运算后得到:
{0 0 1 1 1 0}这个是增加的情况
*/
点赞的话。。。(qwq)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现