在研究
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 imes 计算代价时间))。
分析下和分治法的不同:分治法在计算代价时,分治法同一层可以一起扫来算(有些东西只能这么算),而不用预处理或快速计算代价函数。
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("
")
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
");
return;
}
pp("%.0Lf
", 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("--------------------
");
}
}