IOI2021集训队作业 247OK The Imp

有一堆商品,每个价值为(v_i),代价为(c_i)。收益定义为价值减代价。

你要买一个商品,但是有个小鬼会捣乱,他有(k)次机会使得你正在买的商品的价值变为(0),这时候你不得不重买一遍。

你和小鬼都聪明绝顶,求最终你的收益。

(nle 1.5*10^5,kle 9)


考虑最终的选择策略,即一个选择商品的序列。你按照这个序列选,使得小鬼怎么搞最终的答案都不会更劣。

假如已经知道了这个序列的组成,现在要排序:假如相邻的两个商品(a)(b)(a)在前(b)在后比反过来优,那么有:(min(v_b-c_b-v_a,0)+v_a-c_a>min(v_a-c_a-v_b,0)+v_b-c_b)。拆开来分类讨论一下,发现这等价于(v_a<v_b)

我没有把式子拆完整发现这个东西有传递性而一开始不会证QAQ。

先排序,于是就可以DP了。由于正着DP需要记(sum c_i)比较难受,所以可以倒着DP,每次枚举这一位选或不选,看看小鬼是否在这一位搞,即(f_{i,j})表示搞完([i,n]),小鬼搞了(j)次的答案。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 150005
#define INF 1000000000
int n,k;
struct item{
	int v,c;
} a[N];
bool cmp(item a,item b){
//	return min(b.w-a.v,0)+a.w>min(a.w-b.v,0)+b.w;
	return a.v<b.v;
}
int f[N][10];
int main(){
	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d",&n,&k);
		for (int i=1;i<=n;++i)
			scanf("%d%d",&a[i].v,&a[i].c);
		if (n<=k){
			printf("0
");
			continue;
		}
		sort(a+1,a+n+1,cmp);
		for (int j=1;j<=k;++j)
			f[n+1][j]=0;
		f[n+1][0]=0;
		for (int i=n;i>=1;--i){
			f[i][0]=max(f[i+1][0],a[i].v-a[i].c);
			for (int j=1;j<=k;++j)
				f[i][j]=max(f[i+1][j],min(a[i].v-a[i].c,f[i+1][j-1]-a[i].c));
		}
//		for (int i=1;i<=n;++i,printf("
")){
//			for (int j=0;j<=k;++j)
//				printf("%d ",f[i][j]);
//		}
		printf("%d
",f[1][k]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/13958474.html