满足决策单调性的 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");
	}
}
posted @ 2020-07-24 22:15  Cold_Chair  阅读(231)  评论(0编辑  收藏  举报