P1912 题解
前置知识:决策单调性优化DP
四边形不等式:对于函数
g(x,y) ,如果所有整数a \le b \le c \le d ,都有g(a,d)+g(b,c) \ge g(a,c)+g(b,d) ,则函数g 满足四边形不等式,为凸函数 (可以记为包含大于等于交叉)另一种形式(常见):对于函数
g(x,y) ,如果所有整数a<b ,都有g(a,b+1)+g(a+1,b) \ge g(a,b)+g(a+1,b+1) ,则函数g 满足四边形不等式,为凸函数
如果DP转移方程为
若函数
证明
现在分析此题,先想朴素DP,设
-
f_i=min(f_j+|s_i-s_j-(i-j+1)|^P)
意为前
令函数
好了我们证明完了,此时
当求出
逐一修改时间复杂度太高,使用单调队列
建立一个结构体
初值设
- 取出队首
(w_s,l_s,r_s) ,若r_s \le i ,表示上一个最优决策的适用范围在i 之前,那么直接删除,否则令l_s=i ,缩小范围 (要使用while) - 取出队首保存的最优决策
w 进行转移,得到f_i - 插入新决策
i
-
取出队尾
(w_t,l_t,r_t) -
如果对于
f_{l_t} ,i 比w_t 决策更优(f_i+g(i,l_t) \le f_{w_t}+g(w_t,l_t) ),由于单调性,对于整个区间[l_t,r_t] ,i 都会比w_t 更优,所以令k=l_t ,删掉队尾,返回步骤1 (要使用while) -
如果对于
f_{r_t} ,w_t 比i 决策更优,表示无需更改队尾,则去往步骤5 -
否则,在
[l_t,r_t] 中二分查找位置k ,满足k 之前的决策优于i ,之后的i 更优,将[l_t,r_t] 更改为[l_t,k-1] -
把
(i,k,n) 加到队尾,相当于决策i 的适用范围是[k,n] -
整体逻辑就是新的决策
i 是否比旧的w_t 更优,如果想(2)全都更优,则i 的决策范围涵盖整个[l_t,r_t] ,否则如(4)找到那个令i 更优的位置k ,最后加入新决策i 及其范围,因为右边界无法计算,所以设定为n
二分边界为
优化感性理解:首先,不断删除已经不适用的队头,最后剩的队头就是最优决策点,可依此进行转移;其次,将加入新的点
总时间复杂度
#include <bits/stdc++.h>
using namespace std;
long long n,m,p,s[500005],k[500005],x[100005],z[500021];
long double f[500005];
char c[100005][35];
long double ksm(long double a,long long b)
{
long double ans = 1;
while(b != 0)
{
if(b % 2 == 1)
{
ans *= a;
}
a *= a;
b /= 2;
}
return ans;
}
long double g(long long i,long long j)
{
return f[i] + ksm(abs(s[j] - s[i] + j - i - 1 - m),p);
}
long long bs(long long a,long long b)
{
if(g(a,n) < g(b,n))
{
return n + 1;
}
long long l = b,r = n,ans = n + 1;
while(l <= r)
{
long long mid = (l + r) / 2;
if(g(b,mid) <= g(a,mid))
{
r = mid - 1;
ans = mid;
}
else
{
l = mid + 1;
}
}
return ans;
}
int main()
{
long long t;
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld%lld",&n,&m,&p);
for(long long i = 1;i <= n;i++)
{
getchar();
scanf("%s",c[i]);
s[i] = s[i - 1] + strlen(c[i]);
z[i] = 0;
}
long long hd = 1,tl = 1;
for(long long i = 1;i<=n;i++)
{
while(hd < tl && bs(k[hd],k[hd + 1]) <= i)
{
hd++;
}
z[i] = k[hd];
f[i] = g(k[hd],i);
while(hd < tl && bs(k[tl - 1],k[tl]) >= bs(k[tl],i))
{
tl--;
}
k[++tl] = i;
}
if(f[n] > 1e18)
{
printf("Too hard to arrange\n");
}
else
{
printf("%lld\n",(long long)f[n]);
long long cnt = 0;
for(long long i = n;i != 0;i = z[i])
{
x[++cnt] = i;
}
x[++cnt] = 0;
for(long long i = cnt;i >= 2;i--)
{
for(long long j = x[i] + 1;j <= x[i - 1] - 1;j++)
{
printf("%s ",c[j]);
}
printf("%s\n",c[x[i - 1]]);
}
}
printf("--------------------\n");
}
return 0;
}
注意:这题要求答案超过 Too hard ...,所以使用long long存储不开,要使用long double,但是不仅 (精度坑死人