CF1852C Ina of the Mountain 题解
前言
一个有意思的贪心,但是很少有题解证明了正确性,至少我看着有点迷迷糊糊的,总结一下。
正文
0x00 题目分析
转化一下题目就是给定一个序列
发现这个题的难点在于:
具体证明可以理解为,每当有一个元素上升,那么上升的这一格就不能用之前的区间覆盖掉了,答案贡献
0x01 找到联系
我们不妨想成是把这个值加上了
-
差分序列的贡献。
-
区间加
k 的贡献。
考虑区间加
-
对于
b_i\le0 时,选择这个决策的答案贡献为b_i ;否则答案贡献为0 ,且容易证明b_i\leq 0 时无脑选择这个决策答案不会更劣。 -
选择一个左端点
j ,对于\left[j,i\right) 区间元素加上k ,由于加上了k 在区间内部的元素大小相对作差关系不会变化,则无答案贡献;而区间两个端点的贡献只有左端点,应为右端点直接变成下降,计算左端点答案贡献为b_j+k-\min(b_j,0) 应为这个地方差分会变大,新的贡献是b_j+k ,而原来的贡献是\min(b_j,0) ,并且b_j+k\geq0 。
可以从这个地方开始 DP 了,但是实际上不需要。
0x02 继续观察性质:
-
一个点不可能作为左端点两次,这样答案只会更劣。意味着我们可以把某个点拿来决策了就扔。
-
选择一段区间加
k ,对其他地方完全不影响可以正常执行。 -
在某一个点选择了一段区间加
k ,在后续中不会出现这里不执行这个操作让答案更劣的情况,这样操作后后续不管想选哪个点作为左端点都不于这个操作冲突,因为这个操作只会影响两个端点,其他点的差分值不变,而左端点和又端点对答案的贡献又是最优的。
那么考虑贪心,用小根堆维护
0x03 代码实现
分类讨论执行:
-
-
- 当 $b_i$ 大于堆顶时,选择堆顶的答案贡献,弹出堆顶并且把 $b_i-k+k$ 放入堆。 - 否则选择 $b_i$。
这种时候直接一开始就放入
AC CODE
#include<bits/stdc++.h>
// #define ONLINE_JUDGE
#define INPUT_DATA_TYPE int
#define OUTPUT_DATA_TYPE long long
INPUT_DATA_TYPE read(){register INPUT_DATA_TYPE x=0;register char f=0,c=getchar();while(c<'0'||'9'<c)f=(c=='-'),c=getchar();while('0'<=c&&c<='9')x=(x<<3)+(x<<1)+(c&15),c=getchar();return f?-x:x;}void print(OUTPUT_DATA_TYPE x){register char s[20];register int i=0;if(x<0){x=-x;putchar('-');}if(x==0){putchar('0');return;}while(x){s[i++]=x%10;x/=10;}while(i){putchar(s[--i]+'0');}return;}
void solve(){
std::priority_queue<int,std::vector<int>,std::greater<int>> Q;
register int i,lat=0,a,diff;
register long long ans=0;
int n=read();
int k=read();
for(i=0;i<n;++i){
diff=(a=read()%k)-lat,lat=a;
if(diff<=0) Q.push(diff+k);
else Q.push(diff),ans+=Q.top(),Q.pop();
}
print(ans);putchar('\n');
return;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout);
#endif
int T=read();
while(T--) solve();
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
总结
这个题考察两个能力,转化困难问题的能力和观察性质的能力。