D. Towers

题意:每个数可以与前一个或者后一个点合并,使得数列被合并成单调不降的序列,求出最少的操作数。

思路:考虑dp

首先有一个很贪的思路,在相同的合并操作数下,需要合并出来的数越小越好,这样后面的数就能以更小的代价跟它匹配。另外,如果一个数满足不降的条件,那么就没必要把它合并掉,这样显然不优。

考虑正难则反。即是把题意转化成求最大划分连续序列数。f[i]表示前i个点能划分出来的最大次数,而g[i][j]表示以i结尾的上一个段操作数为j的合并出来的最小数,有两种策略转移:

1.当前数与上一个数合并。f[i]=f[i-1]  g[i][j]=g[i-1][f[i-1]]+a[i]

2.当前数与第j个数合并,使得j+1-i被分成一块。

do:f[i]=max(f[i],f[j]+1)if(s[i]-s[j]>=g[j][f[j]])

do:g[i][f[i]]=s[i]-s[j]if(f[j]+1>=f[i])

最后答案为min(n-f[n],n-1),复杂度O(n^2)

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 5010;

int n,a[N],f[N],s[N],g[N][N];

void solve(){
	
	memset(g,0x3f,sizeof g);
	f[1]=1;g[1][1]=a[1];//以i为结尾而且分成了j段的最小值 
	for(int i=2;i<=n;i++){
		f[i]=f[i-1];g[i][f[i]]=g[i-1][f[i-1]]+a[i];
		for(int j=i-1;j>=1;j--){
			//j+1 ~ i 分成一段 
			int lst=g[j][f[j]],h=s[i]-s[j];
		    if(h>=lst){
		    	if(f[j]+1>=f[i]){
		    		f[i]=f[j]+1;
		    		if(g[i][f[i]]>h)  g[i][f[i]]=h;
				}
			}
		}
	}
	cout<<min(n-f[n],n-1);//
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}  
	solve();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值