诗人小G和四边形不等式
对于线性的dp
或者说是大致的转移方程可以写成这样的dp,时间复杂度大概是
能否优化主要取决于的内容和的范围
假如的范围是一个单调向后移动的窗口,只要能够用多项式表达出来,那就是可以斜率优化或者单调队列优化
但是如果不能表达,而是只能够满足一些性质(也就是比较抽象的函数),斜率优化和单调队列当然是用不了的
因为这两个都对中和之间的关系有要求
但是在抽象的函数中,val不可以被写为i和j的多项式
分析这个具体的性质就不行了
这个时候就可以考虑四边形不等式了(斜率优化还真的不是线性dp的极限。。。)
已经是线性dp,也就是状态上的优化已经是极限了,再优化只能够考虑决策点的优化,即排除无用决策,快速找到最优决策
——————————————————————————————————————————————————————————
当一个函数满足下述性质的时候,我们称其满足四边形不等式
其中可以更加扩展
可以证明,上下两种表述等价(数学归纳法)
下面说说四边形不等式如何优化线性dp
对转移方程,我们设表示的最优决策点,假如具有随着的增大,非严格单调递增,那我们称该转移方程具有决策单调性
很明显,这是非常优秀的性质,可以帮助我们快速找到决策点
定理
证明:
例题
[NOI2009] 诗人小G
题目描述
小 G 是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小 G 给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小 G 不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小 G 对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的 次方,而一个排版的不协调度为所有行不协调度的总和。
小 G 最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。
输入格式
输入文件中的第一行为一个整数 ,表示诗的数量。
接下来为 首诗,这里一首诗即为一组测试数据。每组测试数据中的第一行为三个由空格分隔的正整数 ,其中: 表示这首诗句子的数目, 表示这首诗的行标准长度, 的含义见问题描述。
从第二行开始,每行为一个句子,句子由英文字母、数字、标点符号等符号组成(ASCII 码 ,但不包含 -
)。
输出格式
于每组测试数据,若最小的不协调度不超过 ,则第一行为一个数,表示不协调度。接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。
如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。若最小的不协调度超过 ,则输出 Too hard to arrange
。每组测试数据结束后输出 --------------------
,共 20 个 -
,-
的 ASCII 码为 45,请勿输出多余的空行或者空格。
样例 #1
样例输入 #1
4
4 9 3
brysj,
hhrhl.
yqqlm,
gsycl.
4 9 2
brysj,
hhrhl.
yqqlm,
gsycl.
1 1005 6
poet
1 1004 6
poet
样例输出 #1
108
brysj,
hhrhl.
yqqlm,
gsycl.
--------------------
32
brysj, hhrhl.
yqqlm, gsycl.
--------------------
Too hard to arrange
--------------------
1000000000000000000
poet
--------------------
提示
样例输入输出 1 解释
前两组输入数据中每行的实际长度均为 ,后两组输入数据每行的实际长度均为 。一个排版方案中每行相邻两个句子之间的空格也算在这行的长度中(可参见样例中第二组数据)。每行末尾没有空格。
数据规模与约定
测试点 | ||||
---|---|---|---|---|
所有句子的长度不超过 。
表示排版了前个句子,并且最后一行已经排满时的最小协调度
但是事实上,这个决策的单调性还是非常明显,不需要用四边形不等式证明,或者说,用四边形不等式证明更复杂。。
所以这题我认为其实应该算是决策单调性的例题而不仅仅是四边形不等式的例题。
如何判断决策单调性?
1.四边形不等式
2.从定义出发,对于,若函数和只有一个交点,在这个交点前是更小,在这个交点后是更小,那么它就是满足决策单调性的
我们可以考虑在什么情况下,定义会满足
要使定义满足,其实就是要求两个函数只有一个焦点
1.直线
但是都是直线了,为什么不斜率优化?
2.没有不可拆解的自变量和常量的多项式,否则就不行
因为这个情况下,图形的形状不能保证,就像是和不能保证他们只有一个交点一样。要是能保证,那和斜率优化没什么区别了。。当然,这个有更简单的表述,就是两个不同函数之间可以通过平移变换,也就是对不同的可以找到使得
3.函数形状相同,但是不代表只有一个交点啊,要保证这个函数经过平移只有只能和原本的自己只有一个交点,(应该)可以证明,就是这个函数不能够有拐点
也就是函数的一阶导数单调递增或者单调递减,其实就是这个函数在定义域上是一个凸或凹函数
仔细想想嗷,这后面两个性质,明显只是决策单调性存在的充分条件(不然呢)。
但是这个和四边形不等式似乎是等价的,额,好像是弱化了
对于四边形不等式,我们假设是由作为决策点转移到的,则
那么这个式子的含义可以尝试直接理解,也就是
假如,函数图形是一样的,也就是他们是平移的关系,那么,上面的式子就是这两函数,这两个点之间落差大小的比较,也就是要让这两个函数都为凹函数的同时和比是向左平移,或者都是凸函数的同时是向右平移
这两种情况下都是可以证明上面的这个玩意成立的,也就是四边形不等式得证,转移方程具有决策单调性
这个分析过程。。限制好多,而且想着为什么感觉更麻烦了,还弱化了。。
为什么我不直接证明四边形不等式呢。。。。
我写写这题的四边形不等式证明吧
要证明对于
只需证明
记
只需证明
显然,,所以只需证明对于任意c在其定义域上单调递增即可
这个。。应该是显然成立的吧?
所以得证了
这个过程。。我现在感觉我自己写不出来
其实四边形不等式都可以转化为函数单调性证明,主要是抓住变量和不变量,上面这个式子可以转化的关键,我感觉是第二项里面的是相同的,而i在这里面被视作一个不变量,所以这个是一个常数。
我个人感觉,这个和证明只有一个拐点是非常相似的,但是我有感觉是有区别的
无所谓,我还是用这个把,条件更弱,结论更强,也没麻烦多少。。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
char c=getchar();ll a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n,L,p,l,r;
long double sum[100001],f[100001];ll le[100001],ri[100001],q[100001],track[100001],ans[100001],al;
string a[100001];
long double qpow(long double a,ll b)
{
long double ans=1;
for(;b;b>>=1,a*=a) if(b&1)ans*=a;
return ans;
}
inline long double calc(ll i,ll j)
{
return f[i]+qpow(abs(sum[j]-sum[i]-L-1),p);
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("3.out","w",stdout);
ll T=read();
while(T--)
{
n=read(),L=read(),p=read();
memset(f,0,sizeof(f));memset(sum,0,sizeof(sum));
memset(ri,0,sizeof(ri));memset(le,0,sizeof(le));
memset(ans,0,sizeof(ans));al=0;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
sum[i]+=a[i].size()+sum[i-1]+1;
}
l=1,r=1;
q[l]=0;le[0]=1;ri[0]=n;
for(ll i=1;i<=n;i++)
{
while(l<=r&&ri[q[l]]<i)l++;
f[i]=calc(q[l],i);
track[i]=q[l];
while(l<=r&&calc(i,le[q[r]])<=calc(q[r],le[q[r]]))r--;
ll LL=le[q[r]],rr=n+1;
while(LL<rr)
{
ll mid=LL+rr>>1;
if(calc(i,mid)<=calc(q[r],mid))rr=mid;
else LL=mid+1;
}
if(LL>n)continue;
ri[q[r]]=LL-1;
q[++r]=i;
le[i]=LL;ri[i]=n;
}
if(f[n]<=1e18)
{
printf("%lld\n",(ll)(f[n]));
al=0;
for(int i=n;i;i=track[i])ans[++al]=i;
int cur=al,tmp=0;
for(int i=1;i<=n;i++)
{
if(tmp) putchar(' ');
cout<<a[i];
if(i==ans[cur])
{
cout<<endl;
cur--;
tmp=0;
}
else
tmp++;
}
}
else
cout<<"Too hard to arrange"<<endl;
puts("--------------------");
}
return 0;
}
/*
1
2 4 10
aaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa
*/
我居然还写了个对拍。。
结果最后发现只是long long爆炸了。。
要用long double。 想不到啊。。浪费我2个小时
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!