【dp&减少状态】轻松爬山 UVA12170

针对给定数量的山脉和最大允许高度差,通过预处理和动态规划算法确定将所有相邻山脉高度差限制在指定范围内的最小调整成本。采用离散化技术和优先队列优化策略实现高效求解。

题意,给定n(<=70)座山的高度和d(<=1e9),每座山可以花x的代价使高度增加x或减少x,使得两座相邻的山高度差<=d,求最小代价,无解输出impossible

首先大致想到,可以用f(i,k)表示已经调整完了第i座山,第i座山调整成了高度k,花费的最小代价

但是很明显,k的范围是1e9,显然不通,所以就要想办法简化这一维

当n=3,只能调整第二座山,第二座山调整后的范围应为(h1-d,h1+d)与(h3-d,h3+d)的交集(max(h1,h3)-d,min(h1,h3)+d),再思考一下是不是这个区间里的数都要考虑呢?最优只有三种决策,当h2在范围中,不调整。当h2<下界,调整至下界。当h2>上界,调整至上界。

所以推广一下,每座山的高度就是(hi(1<=i<=n)+kd(-n<k<n)),虽然我也不会证明,这样就预先n^2把这些值处理出来,拍一下序,就相当于把d离散化到n^2的数量级,dp[i][x]代表第x大的d值(记得去重,以及把负数删掉)

在考虑转移时,dp[i][x]就是在dp[i-1][x-d---x+d]中找一个最小值转移,再加上常数|h[i]-x|,这样就可以用优先队列来优化。看一个dalao的代码,发现其实可以不用单调队列。借用这个思想,去重后设有m个d值,对于一个固定的i,dp[i][1--m]是一个单峰有最小值函数。对于一个一个区间内肯定单调或单峰。

优先队列:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF (2100000000000000)
using namespace std;
int H[200],n,d,m;
LL A[200*400],dp[2][250*250];
struct Node{
	int Hei;
	LL val;
	Node(){}
	Node(int hei,LL V){
		Hei=hei; val=V;
	}
}Q[2001010];
LL Abs(LL a){return a<0?-a:a;}
void Work(){
	m=0; int i,j;
	scanf("%d %d",&n,&d);
	for (i=1;i<=n;i++) scanf("%d",&H[i]);
	for (i=1;i<=n;i++) for (j=-n+1;j<n;j++){
		A[++m]=(LL)H[i]+(LL)d*(LL)j;
		if (A[m]<0) m--;
	}
	sort(A+1,A+m+1);
	m=unique(A+1,A+m+1)-A-1;
	for (i=1;i<=m;i++){
		dp[1][i]=dp[0][i]=INF;
		if (H[1]==A[i]) dp[0][i]=0;
	}
	int cur=0,pre=1;
	for (i=2;i<=n;i++){
		swap(cur,pre);
		int front=1,tail=0,k=1;
		for (j=1;j<=m;j++){
			while (k<=m&&Abs(A[k]-A[j])<=d){
				while (tail>=front&&dp[pre][k]<=Q[tail].val) tail--;
				Q[++tail]=Node(A[k],dp[pre][k]);
				k++;	
			}
			while (tail>=front&&Abs(Q[front].Hei-A[j])>d) front++;
			if (front>tail||Q[front].val==INF) dp[cur][j]=INF;
			else dp[cur][j]=Q[front].val+Abs(A[j]-H[i]);
		}
	}
	for (i=1;i<=m;i++)
		if (A[i]==H[n]){
			if (dp[cur][i]>=INF) printf("impossible\n");
			else cout<<dp[cur][i]<<endl; 
			return;
		}
}
int main(){
	int Test_case;
	scanf("%d",&Test_case);
	while (Test_case--) Work();
	return 0;
}

简化:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF (2100000000000000)
using namespace std;
int H[200],n,d,m;
LL A[200*400],dp[2][250*250];
LL Abs(LL a){return a<0?-a:a;}
void Work(){
	m=0; int i,j;
	scanf("%d %d",&n,&d);
	for (i=1;i<=n;i++) scanf("%d",&H[i]);
	for (i=1;i<=n;i++) for (j=-n+1;j<n;j++){
		A[++m]=(LL)H[i]+(LL)d*(LL)j;
		if (A[m]<0) m--;
	}
	sort(A+1,A+m+1);
	m=unique(A+1,A+m+1)-A-1;
	for (i=1;i<=m;i++){
		dp[1][i]=dp[0][i]=INF;
		if (H[1]==A[i]) dp[0][i]=0;
	}
	int cur=0,pre=1;
	for (i=2;i<=n;i++){
		swap(cur,pre);
		int k=1;
		for (j=1;j<=m;j++){
			while (k+1<=m&&Abs(A[k]-A[j])>d) k++;
			//如果k在合法区域外,移到合法区域下界 
			while (k+1<=m&&Abs(A[k+1]-A[j])<=d&&dp[pre][k]>=dp[pre][k+1]) k++;
			//找到极值处                         //上面是>=不是> 
			if (Abs(A[k]-A[j])>d||dp[pre][k]==INF) dp[cur][j]=INF;
			else dp[cur][j]=dp[pre][k]+Abs(A[j]-H[i]);
		}
	}
	for (i=1;i<=m;i++)
		if (A[i]==H[n]){
			if (dp[cur][i]>=INF) printf("impossible\n");
			else cout<<dp[cur][i]<<endl; 
			return;
		}
}
int main(){
	int Test_case;
	scanf("%d",&Test_case);
	while (Test_case--) Work();
	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值