像潮落潮涌,送我奔向自由。|

寂静的海底

园龄:3年2个月粉丝:59关注:15

2023-02-05 19:41阅读: 83评论: 0推荐: 0

CF1768F Wonderful Jump

大神仙题,2023年度最好题预定。

首先有显然的 dp :fi 表示跳到 i 的最小代价。

fi=minj<ifj+(ij)2×min(ajai)

试图优化:

经过各种手玩,发现没有决策单调性,线段树也无法维护这种新的复杂的变化,转移次数也可能较多,并不是常见的优化。

发现值域是 n, 必然有猫腻:

(其实我最开始觉得的一定是从 ini1 转移过来,如果你也这么觉得过,读完这篇题解你会发现它是错的。)

因为平方项的出现,当最小值很大的时候一步跳过去是很劣的是 O(len2×n),我们会跳一个较大的跨度仅当它的最值很小,否则还不如一步一步跳过去的最坏 O(len×n) 级别。

比较两种情况的大小 min×len2<n×len

得知 min×len<n 时才有可能更优。

由于值域是 n ,考虑阈值分治:

lenB 时,直接暴力转移,时间复杂度 O(B)

len>B 时,根据上面的分析,minnB 时,答案才有可能更优,否则一定是无效的。

引出一个很好猜到的引理:

jki ,且 ak=min{ajai} 时,经过 k 一定比不经过 k 优 ,因为平方和本来就小于和平方,乘上的系数也不大于后者。

这个引理告诉我们:对于一段连续的相同值的数,如果被经过,先跳到开头,接着一个一个地调到末尾,再跳出去是最优的。

于是我们就只需要考虑 minnB 这些 O(nB) 个段的最右端的转移,单调栈维护即可。

但是这么做 WA on 5 了,百思不得其解后我看了官方题解,发现漏掉了一种情况:

min{ajai}=ai 时,可能从某个不是单调栈中段最右端的转移且不是 lenB 的段处转移来,因为前面那个引理引出的结论成立的条件是 “那一段连续的数是最小值”。

所以当 ai 是最小值时,可能有不同于前两种转移的转移:令左边第一个大于等于的 ai 的数为 Li,暴力枚举 Li+1i1 转移过来,再之前的都满足最值在转移起点了。

这部分的时间复杂度分析:均摊 O(nB)

考虑如下证明:

考虑每个转移起点对复杂度的贡献,这个点最多贡献给 O(nB) 个转移终点,因为相等的点会阻断转移,而向后一定是一段单增的指可能受到贡献,所以总转移的次数为 O(n2B),均摊 O(nB)

B=n 时总复杂度为 O(nn),空间 O(n)

代码:

//省略快读及宏定义.
signed main()
{
read(n);
rep(i,1,n) read(a[i]) ;
memset(f,0x3f,sizeof f) ;
f[1] = 0 ;
sta[++top] = 1 ;
int x = sqrt(n) ;
for(int i = 2 ; i <= n ; ++ i) {
int mn = a[i] ;
for(int j = i - 1 ; j >= 1 && j >= i - x - 3 ; -- j) {
// 暴力
upmin(mn , a[j]);
upmin(f[i] , f[j] + sq(i - j) * mn) ;
}
while(top && a[sta[top]] > a[i]) top -- ;
sta[++top] = i ;
for(int j = 1 ; j <= x + 1 && j < top ; ++ j) {
upmin(f[i] , f[sta[j]] + sq(i - sta[j] ) * a[ sta[j]]);
// 从单调栈最右端点转移
}
if(a[i] < x + 3) {
// mn = a[i] ;
for(int j = i - 1 ; j >= 1 ; -- j) {
if(a[i] >= a[j]) break;
upmin(f[i] , f[j] + sq(i - j) * a[i]) ;
}
}
}
rep(i,1,n) wrt(f[i],' ') ;

return 0;
}

posted @   寂静的海底  阅读(83)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起