2018.10.24【校内模拟】小 C 的数组(二分答案)(DP)

传送门


解析:

一看就是二分,然而这个 D P DP DP我只会 O ( n 3 ) O(n^3) O(n3)的区间 D p Dp Dp做法。。。
一看觉得可做,考场上就去推式子去了,结果推挂了。。。
一看题解,均分的思想。。。我去我第一个想的就是这个正解,结果被我莫名其妙的否了啊啊啊啊?!!!!

思路:

其实考虑如果只允许改变一个怎么选择,直接选择差最大的一组中的一个,调整成均分就行了。

比如我们现在有一组数据 1 , 5 , 13 , 19 1,5,13,19 1,5,13,19,只允许调整一个,那么我们肯定选择 5 5 5 13 13 13中的一个,调整至这个数的两边均分,显然这里将 5 5 5调整成 7 7 7是最优的。

那么现在这里要求调整多个数,怎么办?

显然如果我们能够调整相邻数之差到不超过 x x x,那么必然存在方案不超过 x + 1 x+1 x+1,即解的存在性是随 x x x单调的。考虑二分。

那么怎么验证,考虑上述的均分的思想,如果原数列中 a b s ( a i − a j ) &lt; = ( j − i ) × x abs(a_i-a_j)&lt;=(j-i)\times x abs(aiaj)<=(ji)×x,那么我们可以通过调整 i &lt; k &lt; j i &lt; k &lt; j i<k<j的所有 a k a_k ak来使得 i − j i-j ij这段区间满足条件,即调整 j − i − 1 j-i-1 ji1个数。

那么怎么求出需要调整的最少个数?考虑 D p Dp Dp

我们用 f i f_i fi表示保持 i i i不变,令区间 i − n i-n in满足性质需要的最少调整次数。
那么状态转移就很好想了 f i = m i n { n − i , m i n { f j + j − i − 1 } ( a b s ( a i − a j ) &lt; = ( j − i ) × x ) } f_i=min\{n-i,min\{f_j+j-i-1\}(abs(a_i-a_j)&lt;=(j-i)\times x)\} fi=min{ni,min{fj+ji1}(abs(aiaj)<=(ji)×x)}

那么对于每个二分出的 x x x,这样 D P DP DP一下,再判断是否能够满足不超过 k k k次就行了。

代码中的 c h e c k check check用了一个贪心的小剪枝,显然我们可以通过把前 i − 1 i-1 i1个数调整与 a i a_i ai相等,使得满足 x x x的限制,这时候就会调整 f i + i − 1 f_i+i-1 fi+i1个数,贪心判断一下就好了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline int getint(){
	re int num;
	re char c;
	re bool f=0;
	while(!isdigit(c=gc()))if(c=='-')f=1;num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return f?-num:num;
}

cs int N=2003;
int n,K;
int a[N];
int f[N];

inline bool check(int x){
	for(int re i=n;i;--i){
		f[i]=n-i;
		for(int re j=i+1;j<=n;++j){
			if(abs(a[j]-a[i])<=1ll*x*(j-i))f[i]=min(f[i],f[j]+j-i-1);
		}
		if(f[i]+i-1<=K)return true;
	}
	return false;
}

int maxn;
signed main(){
	n=getint();K=getint();
	for(int re i=1;i<=n;++i){
		a[i]=getint();
		maxn=max(maxn,abs(a[i]-a[i-1]));
	}
	int l=0,r=maxn;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值