\[f[i][j]表示前i个数字,然后最后一个数字是a[i]+k\times j个区间的结尾,干掉所有章鱼的最小代价 \]
这个是我最开始想的dp的状态。然后很明显\(j\)是可以挺大的,但是如果你不去证明而是感性理解(瞎猜)的话,就很有可能得到\(j\in[0,1]\)的结论。然后用这个写出来一个\(O(n)\)的dp,快乐的wa在test5
(别问我怎么知道的)
事实上,j是可以很大的,大到\(n/2*k\)的级别。
具体的构造方法就是,构造一个多峰的序列,其中从中间往两边谷底大小逐渐上升,中间最低。
但是我的理性告诉我,前后两个数字之间一定是有一些联系的。
从头开始分析吧。
对于这种区间覆盖的操作,还是很熟悉的。需要的总操作数就是最终高度的差分数组的所有的正值的和。
抽象一下,我们的目标就是对每个数字进行+k,来使得这个总操作数最小。
那我们就考虑一下,对于一个确定的数组,我们进行什么样的操作,能使得答案变优秀
先下点定义。如果一个位置的差分数组是正数,我们就称其为一次上升(ascent),如果为负数,就称其为下降(descent)
显然,上升是会使得答案增加的,而下降则是不会对答案产生影响
下降不产生影响,其实就是边界处的章鱼总是不会重生,因为它重生是一定不会让答案变优秀的。
这是可以显然得到的结论。
所以,需要通过对\(a[i]\)来加上一些\(k\)构造的数组,开头和结尾一定是不会被加上k的。
其实我们要使用的结论和这个是有一定的相似性的。这个结论应该是和这个区间覆盖和这个+k的操作强相关。
因为它产生的原因是这个区间覆盖的答案统计的特性,和这个+k操作的特性。就是下面的引理部分。
设\(b[i]=a[i]\mod k\),
假设我们求解到的最优的答案是\(c[i]\),就是对\(a[i]\)加上数个或者零个\(k\)得到的数组
我们的最优选择策略就是,每当我们能够让\(c[i]\)选择小的情况的时候,让他选择小的情况。
如果无法选择变小的情况,就找到前面(这里也可以)可以变大,并且代价最小的部分,使用这次变大的机会,然后让这里变小。
引理
对于答案\(c[i]\),假如\(c[i+1]-c[k]>k\),那我直接让\(c[i+1]-k\),是肯定不会让答案更劣的。
这是显而易见的,一共就两种情况,一种是,后面的\(c[i+2]>c[i+1]\),那\(c[i+1]\)变化后,明显答案不变
第二种,\(c[i+2]\leq c[i+1]\),那变化后答案甚至更优秀了
综上,答案不会更劣。
那么对于每个位置,可供选择的数字一共就两个,如果\(a[i]\mod k = a[i-1]\mod k\),那甚至就只有一个情况。
很明显,对于最后的答案,我们是要求尽可能小的,所以对于每个位置,我们都优先选择其中更小的情况,因为这样不需要先给答案加上代价。
假如这次无法选择更小的也就是不得不让答案变得更大,那最优的也是挑选前面的一次没有选择过的且使答案增加最少的位置来增加我们这次需要的k,让这次不需要再付出代价。因为在前面付出代价和在这里付出代价产生的效果是等价的,那肯定是选择代价最小的一次。
这个操作和我的dp很像,区别在于一个是相对的+k,而一个是绝对的+k。
我想不到的原因,我感觉在于我对这个区间覆盖的答案统计的方式不够熟悉。
让我想我肯定是能想到的,但是就是因为不够熟悉,所以在对他使用的使用的时候就不能很灵活了。
而且这个反悔贪心确实是能用巧妙来形容。可能我知道了需要用的引理,也不会去写这个反悔贪心,而是会写图论类似的东西。
虽然最短路也是\(O(nlogn)\),但是这个题解的代码确实是简洁而优美的。
思路应该是要逐渐往后的,就是从\(a[1]\)开始,往后想,要怎么让\(a[i+1]\)的位置变化才能求到更优的答案。这样每一个位置相对上个位置只能有两个选择的引理就很好看出来了。
然后就是下降优先的分析。
其实这个很好做,因为我每次下降不付出代价,而上升则是要付出代价,那自然是贪心的取,答案会更小。
但是如何证明正确呢?
这个就是反悔贪心了。
首先,如果答案原始是最优秀的,我们要证明证明,用这种贪心生成的下一步的答案依旧是优秀的?
很明显啊。如果这里能继续下降,那自然是下降更优秀。如果不能,那最优的也是挑选前面的一次没有选择过的且使答案增加最少的位置来增加我们这次需要的k,让这次不需要再付出代价。很明显,没有更优秀的选择。
那从1开始,只有一个数字的时候,自然是不加k的时候最优秀。
数学归纳法,证明了。
但是为什么我还是觉得有地方不够清晰?
或者说,让我再独立的思考一遍这个过程, 我会不会还有地方是依靠记忆而不是自然的思考?
1.从左往右,考虑如何构造一个答案最小的答案。
2.通过上面发现引理,但是我要怎么样才能通过引理来想到这反悔贪心的做法?
对于我来说,图论的做法更自然。
但是这个始终向下的贪心,是不在我自然思考的范围内的。
把他加进去。
以后的事情,可以以后再反悔。
很奇妙,真的,这就像一个后效性十分确定的dp一样。我前面的每一个决策产生的后效性都是确定的,以至于我可以在后面操控前面的决策改变的同时利用后效性的变化。
第一次写到,这个上升下降的模型,可以记一下。留个印象。
如果这个后效性的想法能在脑力子里面成型,那就可以想到这个优先队列的想法。
也就是,我们要注意到“改变决策产生的影响是可预测的”这一点。如何再配上题目的特性,就可以产生这样的一个反悔贪心
好好好,我现场出一个试试看。
首先,决策是否可多重?
不可以吗?
因为所有的决策都再优先队列中。如果一个点有了多个决策,要么它这多个决策之间产生的影响都能是一样的,要么,对于每点的决策数量就都是固定的,而且保证每次使用后,这个决策点无法再被使用,否则会TLE。但是这个要求其实可实质化为一个性质,既然我每次只能选两个决策点,一个先用着,一个放队列,那其他的我用它干什么呢?如果我不能及时的判断出他们是否会被最终答案使用上,那我能不能判断出最优秀的,就是优先使用的答案是谁呢?
如果我能,为什么我不能判断出剩下的呢?
这个可以用限制保证。假如我这次下降所需的不再是一次上升所能满足的,那我就需要用到区间查询了,是动态的在线区间查询。
用值域线段树可以做到吧。而且需要能够支持删除操作。这个还好。
这样设计的题目,很强硬。而这题,很自然。
这个是我做不到的,也是我觉得这题好的原因。
可以出一个很有意思的dp,这个dp的决策没有及时性,也不需要数组来存放,我们也可以叫他,反悔贪心。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
char c=getchar();int a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
int n,k,a[200001];
int f[200001][5];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=read();
while(T--)
{
n=read(),k=read();
priority_queue<int,vector<int>,greater<int> >q;
ll previous=0,x,ans=0;
for(int i=1;i<=n;i++)
{
x=read();x%=k;
if(x>previous)
{
q.push(x-previous);
ans+=q.top();
q.pop();
}
else
{
q.push(k+x-previous);
}
previous=x;
}
cout<<ans<<endl;
}
return 0;
}
标签:这个,1852C,决策,cf,反悔,答案,dp,贪心
From: https://www.cnblogs.com/HLZZPawa/p/18087607