【洛谷1912】[NOI2009] 诗人小G(决策单调性优化DP)
- 给定\(n\)个诗句,你可以把若干连续的诗句并在一行(一行的诗句间要加空格)。
- 给定一个标准值\(m\)和一个数\(p\),要求最小化每行字数和\(m\)之差的绝对值的\(p\)次方和。
- 数据组数\(\le5,n\le10^5,p\le10\)
暴力\(DP\)
显然\(DP\)时我们不需要考虑诗句内容,只要知道其长度即可。
令\(a_i\)为诗句长度的前缀和,\(f_i\)为考虑了前\(i\)个诗句时的最优答案,显然有暴力\(DP\):
\[f_i=\min_{j=0}^{i-1}\{f_j+|a_i-a_j+i-j-1-m|^p\}
\]
其中\(i-j-1\)表示需要增加的空格数。
决策单调性
不妨设\(Calc(j,i)\)为从\(j\)转移向\(i\)的代价,也就是\(|a_i-a_j+i-j-1-m|^p\)。
显然这个东西不太好拆,很难斜率优化。
所以我们考虑决策单调性。
突然发现自己竟从未写过决策单调性优化\(DP\)这种神奇的东西。。。
对于这道题,我们开一个单调队列,并记下队列中每两个元素之间的决策分界点\(k\),表示在\(k\)之前从前一个元素转移,在\(k\)之后从后一个元素转移。
显然这个单调队列需要满足\(k\)递增。
至于如何求出\(k\),每次要新计算两个元素之间的决策层分界点时,直接二分即可。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB long double
using namespace std;
int n,m,p,a[N+5],S[N+5];char s[N+5][50];
I DB QP(DB x,RI y) {DB t=1;W(y) y&1&&(t*=x),x*=x,y>>=1;return t;}
namespace Monotonicity
{
#define Calc(j,i) (f[j]+QP(abs(a[i]-a[j]+(i)-(j)-1-m),p))//从j转移到i的代价
int k[N+5],q[N+5],g[N+5];DB f[N+5];
I int K(CI x,CI y)//二分分界点
{
RI l=y+1,r=n+1,mid;W(l^r) mid=l+r>>1,Calc(x,mid)>=Calc(y,mid)?r=mid:l=mid+1;return r;
}
I void DP()//决策单调性优化动态规划
{
RI i,H=1,T=1;for(q[1]=0,i=1;i<=n;++i)
{
W(H^T&&k[H]<=i) ++H;f[i]=Calc(q[H],i),g[i]=q[H];//取队首转移,记下决策点方便输出方案
W(H^T&&k[T-1]>=K(q[T],i)) --T;k[T]=K(q[T],i),q[++T]=i;//往队尾加点,维护分界点单调性
}
}
}
int main()
{
using namespace Monotonicity;
RI Tt,i,j,T;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%d%d",&n,&m,&p),i=1;i<=n;++i) scanf("%s",s[i]+1),a[i]=a[i-1]+strlen(s[i]+1);//统计前缀和
if(DP(),f[n]>1e18) {puts("Too hard to arrange");goto End;}printf("%.0Lf\n",f[n]);//判解过大,否则输出答案
for(T=0,i=n;i;i=g[i]) S[++T]=i;for(i=T,j=1;i;--i) W(j<=S[i]) cout<<s[j]+1<<(" \n"[j==S[i]]),++j;//根据决策点输出方案
End:puts("--------------------");
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒