1. 程式人生 > 實用技巧 >滿足決策單調性的 DP 的通用做法

滿足決策單調性的 DP 的通用做法

在研究
http://uoj.ac/contest/37/problem/285
這題時發現了這個東西:
“滿足決策單調性的 DP 的通用做法”

看一道更簡單的例題:

【NOI2009】詩人小G

大概就是:
\(f[i]=min(f[j]+cost(j+1..i))(j<i)\)

然後滿足決策單調性。

一般的決策單調性的題的那種分治是做不了這個的,因為要自己推自己。

考慮還是從左往右dp,維護一個單調佇列,佇列的每個元素形如\((l,r,x)\)表示\([l,r]\)的最優決策點目前是\(x\)

對於\(i\),先利用隊頭求出\(f[i]\),再考慮加入它成為新的決策點。

從佇列尾開始退,如果隊尾的\(l\)

\(x\)處的值都不如\(l\)\(i\)優的話,就退掉這個。

最後剩一個區間,二分分界點即可。

時間複雜度是:\(O(n~log~n \times 計算代價時間)\)

分析下和分治法的不同:分治法在計算代價時,分治法同一層可以一起掃來算(有些東西只能這麼算),而不用預處理或快速計算代價函式。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i ,x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

#define db long double

int T;

const int N = 1e5 + 5;

int n, p; db len;

char s[N][35];
db a[N], pa[N];

struct nod {
	int l, r, x;
} z[N];

db f[N];
int fr[N];

db calc(int l, int r) {
	db s = 1, x = abs(pa[r] - pa[l] + r - l - 1 - len);
	for(int y = p; y; y /= 2, x = x * x)
		if(y & 1) s = s * x;
	return s + f[l];
}

void work() {
	scanf("%d %Lf %d", &n, &len, &p);
	fo(i, 1, n)	{
		scanf("%s", s[i] + 1);
		a[i] = strlen(s[i] + 1);
		pa[i] = pa[i - 1] + a[i];
	}
	int st = 1, en = 1;
	z[1] = (nod) {1, n, 0};
	fo(i, 1, n) {
		while(z[st].r < i) st ++;
		f[i] = calc(z[st].x, i);
		fr[i] = z[st].x;
		
		if(z[st].r <= i) st ++; else
			z[st].l = i + 1;
			
		while(st <= en && calc(i, z[en].l) < calc(z[en].x, z[en].l)) en --;
		
		int as;
		if(st > en) {
			as = i + 1;
		} else {
			as = z[en].r + 1;
		}
		for(int l = z[en].l, r = z[en].r; l <= r; ) {
			int m = l + r >> 1;
			if(calc(i, m) < calc(z[en].x, m)) {
				as = m, r = m - 1;
			} else l = m + 1;
		}
		z[en].r = as - 1;
		if(as <= n) z[++ en] = (nod) {as, n, i};
	}
	if(f[n] > 1e18) {
		pp("Too hard to arrange\n");
		return;
	}
	pp("%.0Lf\n", f[n]);
	static int d[N][2], d0;
	d0 = 0;
	for(int x = n; x; x = fr[x])
		d[++ d0][0] = fr[x] + 1, d[d0][1] = x;
	fd(i, d0, 1) {
		fo(j, d[i][0], d[i][1]) {
			fo(k, 1, a[j]) pp("%c", s[j][k]);
			if(j != d[i][1]) pp(" ");
		}
		hh;
	}
}

int main() {
	scanf("%d", &T);
	fo(ii, 1, T) {
		work();	
		pp("--------------------\n");
	}
}