[斜率优化][性质DP] 模拟赛好题分享

在这里插入图片描述
看数据范围显然是 DP

但简单推推会发现:怎么搞都有后效性!!当前填数必然会对后面的产生影响,而 前面所有数的状态 是不可能在 DP 里记录的

一般遇到这种情况时,我们就要考虑 通过推性质,运用贪心策略排除无用状态,使得剩下的转移无后效性

通过 打表、感性理解、理性证明,我们发现一个性质:

从左往右所填的数一定单调不升

简单的证明:

形式化的,对于 i < j i < j i<j a [ i ] = a [ j ] = 0 a[i]=a[j]=0 a[i]=a[j]=0,若所填数 b b b 满足 b [ i ] < b [ j ] b[i]<b[j] b[i]<b[j] 则一定不优
在这里插入图片描述

考虑邻项交换,交换后, i i i 之前 大于 b [ i ] b[i] b[i] 的仍然构成逆序对, j j j 之后小于 b [ j ] b[j] b[j] 的仍然构成

而在 i , j i,j i,j 之间,大于 b [ j ] b[j] b[j] 的更可以大于 b [ i ] b[i] b[i],小于 b [ i ] b[i] b[i] 的更小于 b [ j ] b[j] b[j],除此以外还可能有新增的贡献
在这里插入图片描述
该性质得证

推出这一点,我们就可以 DP 了, f [ i ] [ j ] f[i][j] f[i][j] 表示考虑了前 i i i 个数,填到 j j j,且 i i i 为最后一个连续段的结尾,最多逆序对

为了方便计算,我们先统计 所有不为零的位置 两两间的贡献,接下来每确定填一个数,就加上其与前后产生的总贡献

转移有: f [ i ] [ j ] = max ⁡ 0 ≤ k ≤ i (   f [ k ] [ j + 1 ] + v a l [ i ] [ j ] − v a l [ k ] [ j ] + ( i − k ) k   ) f[i][j]=\max_{0\leq k\leq i}(\ f[k][j+1]+val[i][j]-val[k][j]+(i-k)k\ ) f[i][j]=0kimax( f[k][j+1]+val[i][j]val[k][j]+(ik)k )

其中 v a l [ i ] [ j ] val[i][j] val[i][j] 表示 前 i i i 个 零的位置都填 j j j ,总贡献(这种前缀思想很重要),是可以 O ( n k ) O(nk) O(nk) 预处理的

对式子变形: f [ k ] [ j + 1 ] − v a l [ k ] [ j ] − k 2 = − i k − v a l [ i ] [ j ] + f [ i ] [ j ] f[k][j+1]-val[k][j]-k^2=-ik-val[i][j]+f[i][j] f[k][j+1]val[k][j]k2=ikval[i][j]+f[i][j]

再复习一遍:只与 k k k 有关的作为 Y Y Y i i i k k k 的乘积作为 k x kx kx,所求值 和 常数作为 b b b

直接上斜率优化即可,注意初值 inf 的问题

#include<bits/stdc++.h>
using namespace std ; 
#define Y(k) (-f[k][j+1]+val[k][j]+1LL*k*k)
#define X(k) (k)
#define tt q.size()-1

typedef long long LL ;
const int N = 2e5+10 , M = 105 ;

int n , K ;
int a[N] , len , g[N] , sum[M] , sumh[M] ;
LL f[N][M] , val[N][M] , ans ;

void pre_work()
{
	for(int i = 1 ; i <= n ; i ++ ) {
		if( a[i] ) {
			ans += sum[a[i]] ;
			for(int j = 1 ; j < a[i] ; j ++ ) {
				sum[j] ++ ;			
			}
			for(int j = a[i]+1 ; j <= K ; j ++ ) {
				sumh[j] -- ;
			}
			continue ;
		}
		g[++len] = i ;
		for(int j = 1 ; j <= K ; j ++ ) {
			val[len][j] = val[len-1][j] + sum[j] + sumh[j] ;
		}
	}
}
deque<int> q ;	

int main() 
{
	scanf("%d%d" , &n , &K ) ;
	for(int i = 1 ; i <= n ; i ++ ) {
		scanf("%d" , &a[i] ) ;
		if( a[i] ) for(int j = a[i]+1 ; j <= K ; j ++ ) sumh[j] ++ ;
	}
	pre_work() ;
	memset( f , 0xcf , sizeof f ) ;
	for(int j = K ; j >= 1 ; j -- ) {
		q.clear() ;
		f[0][j+1] = 0 ;
		q.push_back(0) ;
		for(int i = 1 ; i <= len ; i ++ ) {
			int kk = i ;
			while( q.size()>1 && Y(q[1])-Y(q[0]) <= (X(q[1])-X(q[0]))*kk ) q.pop_front() ;
			int k = q.front() ;
			f[i][j] = max( f[i][j+1] , f[k][j+1] + val[i][j] - val[k][j] + LL(i-k)*k ) ;
			if( f[i][j+1] >= 0 ) { // 合法才加 
				while( q.size()>1 && (Y(q[tt])-Y(q[tt-1]))*(X(i)-X(q[tt])) >= (Y(i)-Y(q[tt]))*(X(q[tt])-X(q[tt-1])) ) q.pop_back() ;
				q.push_back( i ) ;
			}
		}
	}
	printf("%lld\n" , max(0LL,f[len][1])+ans ) ;
	return 0 ; 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值