IOI2021集训队作业 247OK The Imp

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

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

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

\(n\le 1.5*10^5,k\le 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\n");
			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("\n")){
//			for (int j=0;j<=k;++j)
//				printf("%d ",f[i][j]);
//		}
		printf("%d\n",f[1][k]);
	}
	return 0;
}
posted @ 2020-11-11 14:24  jz_597  阅读(127)  评论(0编辑  收藏  举报