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;
}