https://codeforces.com/contest/1853
E. Ina of the Mountain
题意
给出一个长度为 n 的数组 a,满足 1≤ai≤k。每次操作可以选定一个区间 [l,r],让其中所有的数减 1。当一个数变成 0 后,它会立刻变成 k。求最少需要多少次操作,才能让所有的数都变成 k。
题解
一道反悔贪心的题目,感觉很巧妙。
我们先考虑一个经典问题:给出一个数组 a,需要多少次同样的操作能将它变成全 0。答案是 i=1∑nmax(0,ai−ai−1)。
这道题目相当于是可以给一些 ai 加上 k,求上述式子的最小值。我们考虑反悔贪心:
- 若 ai<ai−1,那么该项的贡献为 0。
- 若 ai>ai−1,有两种决策
- 贡献为 ai−ai−1
- 选取某个 j<i,给 aj,⋯,ai−1 都加上 k,贡献为 aj−aj−1+k。
 
我们用一个优先队列来维护所有可能的决策(也就是反悔堆),若 ai<ai−1,则将 ai−ai−1+k 放入队列,否则将 ai−ai−1 放入队列。这是因为我们选择反悔的位置 j 一定满足 aj<aj−1,不然 aj−aj−1+k 大于 k,得到的结果一定不会被选择。
补充说明:对于 ai−1 已经加上了 k,再取 ai−ai−1 的决策,就相当于是给 ai 也加上了 k。
具体细节参考代码。
代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | #include <bits/stdc++.h>using namespace std;
 typedef long long ll;
 int main()
 {
 ios::sync_with_stdio(false);
 cin.tie(nullptr);
 int t;cin>>t;
 while(t--)
 {
 int n,k;cin>>n>>k;
 priority_queue<int,vector<int>,greater<int>> q;
 int lst=0;ll ans=0;
 for(int i=1;i<=n;i++)
 {
 int x;cin>>x;x%=k;
 if(x>lst)
 {
 q.push(x-lst);
 ans+=q.top();
 q.pop();
 }
 else q.push(k+x-lst);
 lst=x;
 }
 cout<<ans<<'\n';
 }
 return 0;
 }
 
 |