例題9-25 輕鬆爬山 UVa12170

1.題目描述:點擊打開鏈接

2.解題思路:本題屬於區間dp類問題,根據題意,我們需要修改2到n-1之間的一些h的值,使得相鄰2個h值差的絕對值不超過d。因爲可以修改的數是一個實數,無法一一枚舉,因此需要仔細對問題進行分析。

首先,考慮只有3個值的情況:h1, h2, h3。那麼根據題意,h2應該在區間[h1-d,h1+d]和[h3-d,h3+d]之間,即h1應該在[max(h1,h3)-d,min(h1,h3)+d]之間。如果這個區間是空集,即abs(h3-h1)>(3-1)*d,那麼自然無解,否則,如果h2就在區間內部,那麼不需要修改;如果h2<max(h1,h3)-d,那麼就修改爲max(h1,h3)-d;如果h2>min(h1,h3)+d,那麼就修改爲min(h1,h3)+d。可以發現,在這個簡單的問題中,h2的最優修改方案只有這3種情況。並且我們還發現了,如果要修改,一定是修改成形如hp+k*d的形式。

接下來我們考慮有n個值的情況,根據類似的推理可以得出:如果abs(hn-h1)>(n-1)*d時候,一定無解。如果滿足這個條件,那麼中間的每個數字要麼不變,要麼變成形如hp+k*d的形式。由於abs(hn-h1)<=(n-1)*d。那麼k的取值範圍最大就是[-n+1,n-1],一共有2n-1種情況,另外,p的取值範圍是[1,n],即hp+k*d一共有O(n^2)種情況。設dp(i,x)表示已經修改了i-1個數,第i個數改成x時候還需要的最小費用。那麼不難得到如下的狀態轉移方程:

dp(i,x)=abs(hi-x)+min{dp(i-1,y)|abs(x-y)<=d};

上述方程表示:當固定了i,x的時候,我們需要考察在區間[x-d,x+d]中的所有y值,並選出其中最小的那個dp值。這樣,需要O(d)時間確定一個狀態,而所有的狀態一共有O(n^3)種,因此總的時間複雜度會高達O(N^4)。這時需要用單調隊列來加以優化:先把區間[x-d,x+d]看做一個固定的區間,區間長度恆爲2d,那麼問題就轉化爲求在該區間內的最小值,如果我們從小到大枚舉x,那麼問題就轉化爲了求一個長度爲2d的滑動區間的最小值,這正可以用單調隊列加以解決,求出每個滑動窗口的最小值的平攤複雜度爲O(1),即我們最終只需要O(1)時間即可確定出最小值,那麼總的時間複雜度降爲O(N^3),可以在時間限制內加以解決。

最後說一點:雖然說是用單調隊列來求每個滑動窗口的最小值,然而再深入分析一步就會發現一個規律,每次算完一個階段後,該階段的dp序列呈現的是一個先下降,後上升的趨勢,而且下降時候的最低點一定是整個序列的最低點。這樣,我們只需要一個front指針即可維護優先隊列了。

3.代碼:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<complex>
#include<functional>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
#define pb push_back
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <ll,ll> P;



const int maxn = 100 + 5;
const int maxx = maxn*maxn * 2;

const ll INF = (1ll << 60);

ll h[maxn], x[maxx], dp[2][maxx];

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n;
		ll d;
		scanf("%d%lld", &n, &d);
		for (int i = 0; i<n; i++)
			scanf("%lld", &h[i]);
		if (abs(h[0] - h[n - 1])>(n - 1)*d)
		{
			puts("impossible"); continue;
		}
		int nx = 0;
		for (int i = 0; i<n; i++)
			for (int j = -n + 1; j<n; j++)
				x[nx++] = h[i] + j*d;//把所有可能用到的修改結果都存放到x數組中
		sort(x, x + nx);
		nx = unique(x, x + nx) - x; //將x數組從小到大排序並去重,那麼0...nx-1之間的所有數就是修改時候可能用到的結果

		int t = 0; //因爲只有奇,偶兩種變化,且i只依賴於i-1時候的情況,因此可以用滾動數組優化
		for (int i = 0; i<nx; i++)
		{
			dp[0][i] = INF;
			if (x[i] == h[0])dp[0][i] = 0; //初始化時候,只有恰好等於h0時候費用爲0,其他均爲INF
		}
		for (int i = 1; i<n; i++)
		{
			int k = 0; //單調隊列的front指針
			for (int j = 0; j<nx; j++)//從小到大枚舉x值,因爲所有需要用到的x值都在x數組中,因此等價於枚舉下標
			{
				while (k<nx&&x[k]<x[j] - d)k++; //找到第一個符合條件的x[k]
				while (k + 1<nx&&x[k + 1] <= x[j] + d&&dp[t][k + 1] <= dp[t][k])k++; //第i處的x值爲x[j],如果不符合後者>前者,那麼就出隊列
				if (dp[t][k] == INF)dp[t ^ 1][j] = INF; //最小的那個dp[t][k]不存在,那麼新的狀態也不存在
				else dp[t ^ 1][j] = dp[t][k] + abs(x[j] - h[i]); //得到x[j]處對應的最小值
			}
			t ^= 1; //更新爲當前狀態
		}
		for (int i = 0; i<nx; i++)
			if (x[i] == h[n - 1])
				printf("%lld\n", dp[t][i]);
	}
}


發佈了470 篇原創文章 · 獲贊 90 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章