CF417 D. Cunning Gena [dp+更改dp选择顺序]

传送门

[前题提要]:其实这种思想博主在很早之前就碰到过了,但是今天碰巧在做了这道题之后感觉这道题额外的典,所以写一篇博客来记录一下


其实看完这道题之后不难想到我们应该状压 d p dp dp来解决本题.因为一共的问题才 20 20 20个,并且每一个人能解决的问题是一个集合,我们也可以使用状态压缩来轻易的记录.

那么我们不难得出一个递推方程,考虑使用 d p [ i ] [ S ] dp[i][S] dp[i][S]来记录当前枚举到第 i i i个人,当前已经能解决的问题集合为 S S S的最小花费.但是此时我们并没有考虑显示器的选择问题.此时我们应该持有一个朴素的想法,如果我们之前选择的人存在一个人的显示器需要个数已经大于了当前枚举到的人,那么当前这个人就不用考虑显示器的问题(原因显然所以此处不再赘述了).所以顺着这个想法我们得想一个解决方案来记录前缀的显示的最大值.然后看一下范围,发现竟然达到了 1 e 9 1e9 1e9,那么此时我们必然不能记录显示器的选择个数了.此时思维陷入了一个死胡同.

但是假设你的做题经验比较丰富,那么你在没什么想法的时候应该往给物品排序的方向去思考.因为对于 d p dp dp问题来说,经常存在以下 t r i c k trick trick:考虑物品贡献的顺序并不会影响最终的最优性策略,但是这种人为的偏序关系有些时候会对递推维护带来格外的收益.

我们考虑排序能不能帮助解决该题.诶,我们发现如果将所有的人按照需要的显示器个数从大到小进行排序,似乎问题就变得简单了起来.考虑枚举到第 i i i个人,如果他是从 S S S状态递推过来,那么如果 S S S不为 0 0 0,那么必然在 i i i之前已经选过某一个人.那么此时前缀的显示器需要个数必然大于等于当前选择的人的需要的,所以当前人可以直接不考虑显示器问题;如果他是从 0 0 0状态递推过来,那么当前选择的人即为以后选择的所有序列的显示器数的最大值,所以加入当前人的显示器花费即可


下面是具体的代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define pd __gnu_pbds
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Node{
	int x,k,state;
	bool operator < (const Node &rhs) const {
		return k>rhs.k;
	} 
}node[maxn];
int dp[1<<20];
signed main() {
	int n=read();int m=read();int b=read();
	for(int i=1;i<=n;i++) {
		node[i].x=read();node[i].k=read();
		int m=read();
		int state=0;
		for(int j=1;j<=m;j++) {
			int num=read();
			state|=(1<<(num-1));
		}
		node[i].state=state;
	}
	sort(node+1,node+n+1);
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int i=1;i<=n;i++) {
		auto [x,k,state]=node[i];
		for(int S=(1<<m)-1;S>=0;S--) {
			if(S==0) {
				dp[S|state]=min(dp[S|state],dp[S]+x+k*b);
			}
			else {
				dp[S|state]=min(dp[S|state],dp[S]+x);
			}
		}
	}
	if(dp[(1<<m)-1]==ll_INF) {
		cout<<"-1"<<endl;
	}
	else {
		cout<<dp[(1<<m)-1]<<endl;
	}
	return 0;
}
  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值