【知识】四边形不等式

大佬博客

四边形不等式

定义:

对于二维函数 W 中满足 abcd  a,b,c,dZ 都有 Wa,d+Wb,cWa,c+Wb,d,则称 W 满足足四边形不等式。

性质:

满足 i<i+1j<j+1  i,jZWi,j+1+Wi+1,jWi,j+Wi+1,j+1  ,那么 W 满足四边形不等式。

证明:

  • i+1<i+2j<j+1

    i=i+1,则有

    Wi,j+1+Wi+1,jWi,j+Wi+1,j+1

    Wi+1,j+1+Wi+2,jWi+1,j+Wi+2,j+1  

    +  Wi,j+1+Wi+1,j+Wi+1,j+1+Wi+2,jWi,j+Wi+1,j+1+Wi+1,j+Wi+2,j+1

    整理可得,Wi,j+1+Wi+2,jWi,j+Wi+2,j+1

  • i+2<i+3j<j+1

    同理可得, Wi,j+1+Wi+3,jWi,j+Wi+3,j+1

所以 :abc<c+1,  a,b,cZ,都有 Wa,c+1+Wb,cWa,c+Wb,c+1  

  • abc+1<c+2

    Wa,c+2+Wb,c+1Wa,c+1+Wb,c+2  

    +  Wa,c+1+Wb,c+Wa,c+2+Wb,c+1Wa,c+Wb,c+1+Wa,c+1+Wb,c+2

    整理可得, Wb,c+Wa,c+2Wa,c+Wb,c+2

  • abc+2<c+3

    同理可得, Wb,c+Wa,c+3Wa,c+Wb,c+3

所以:abcd,都有 Wa,d+Wb,cWa,c+Wb,d

证毕!

为啥叫四边形不等式呢?如下图
image

易证 AD+BCAC+BD

DP 优化:

考虑一种决策型动态规划问题,其状态转移方程如下:

fi=min0j<i{fj+vj,i}.

这种形式的转移方程通常出现在以下类型的问题中:给定 n 个数,需要将它们分成若干连续的区间,并且每个区间都有一个特定的价值,目标是最小化所有区间价值之和。我们令 fi 表示将前 i 个数分成若干段后的最小总价值,枚举 j 就是决定第 i 个数属于哪一段,而 vj,i 则表示这一段的总价值。

为了优化这个问题,引入决策点的概念。我们定义 pi 为一个决策点,它是对于每个 i,在 0j<i 的范围内,使得 fj+vj,i 达到最小值的 j。因此,pi 就是 i 的决策点。

对于这类问题,一般的做法是直接采用朴素的 O(n2) 时间复杂度算法。然而,若 v 满足四边形不等式,我们可以通过以下方法来优化计算过程。

Property 1:

如果 v 满足四边形不等式,那么每个 i 的决策点是单调递增的。

Property 2:

如果存在一个 x<j,使得对于某个 i>j,选取 j 的转移比选取 x 更优,那么对于所有 i>i,选择 j 也将比选择 x 更优。实际上,j 作为 i 的决策点与性质 1 是等价的,而性质 1 实际上是性质 2 的一个特殊情况。

特别要注意的是,x 必须满足 x<j

证明:

由于 jxi 的贡献更优,所以我们有:

fj+vj,ifx+vx,i.

同时,由于 v 满足四边形不等式,意味着:

vx,i+vj,ivx,i+vj,i,

这可以推导出:

vj,ivj,ivx,ivx,i.

将上述不等式与原不等式相加,得到:

fj+vj,ifx+vx,i.

如何优化 DP ?

首先,我们设定初始时每个 i 的决策点都为 0,然后从前往后依次计算每个 fi,并尝试用当前的 i 作为新的决策点来更新其他的状态。

基于性质 2,如果某个 fi 能更新 fx,那么它同样能够更新从 fx+1fn。也就是说,fi 能更新的点总是处于一个连续的尾段。我们可以通过二分法来高效地找到这个更新区间。

为了进一步优化,我们可以维护一个单调队列,其中每个元素表示一个三元组 (l,r,c),其中 lr 表示在区间 [l,r] 内,每个点的最优决策都是 c。我们通过这个队列可以高效地找到 p 的位置。对于每个 i,我们先从队列中找到该位置 p 的区间,然后在 [l,r] 区间内二分查找更新的决策点,并相应更新队列。

另外,也可以采用分治法来进行优化,这样的时间复杂度同样是 O(nlogn)

题目梳理:

  • P3195 [HNOI2008]玩具装箱

    考虑一个 O(n2) 的 dp。

    fi 表示前 i 个玩具的最小总费用,si 表示前 i 个玩具的长度和,即前缀和。

    有转移方程:fi=min0j<i{fj+(sisj+ij1l)2}。在这题中前文的 v 即后面的 (sisj+ij1l)2,我们只需要证明 v 满足四边形不等式即可。

    Q=S[i]S[j]1L

    w(i,j)=(S[i]S[j]1L)2=Q2

    w(i+1,j+1)=(S[i+1]S[j+1]1L)2 =((S[i]+C[i+1]+1)(S[j]+C[j+1]+1)1L)2 =(Q+C[i+1]C[j+1])2

    w(i,j+1)=(S[i]S[j+1]1L)2=(S[i](S[j]+C[j+1]+1)1L)2=(QC[j+1]1)2

    w(i+1,j)=(S[i+1]S[j]1L)2=((S[i]+C[i+1]+1)S[j]1L)2=(Q+C[i+1]+1)2

    w(i,j)+w(i+1,j+1)=2Q2+2C[i+1]Q2C[j+1]Q+C[i+1]22C[i+1]C[j+1]+C[j+1]2

    w(i+1,j)+w(i,j+1)=2Q2+2C[i+1]Q2C[j+1]Q+C[i+1]2+2C[i+1]+2C[j+1]+C[j+1]2+2 w(i,j)+w(i+1,j+1)w(i+1,j)+w(i,j+1)=2(C[i+1]+1)(C[j+1]+1)

    C[i],C[j]12(C[i+1]+1)(C[j+1]+1)8

    w(i,j)+w(i+1,j+1)w(i+1,j)+w(i,j+1)

    Code
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    const int MAXN = 50050;
    int N, L, dp[MAXN], sum[MAXN], f[MAXN], g[MAXN], h, t, Q[MAXN];
    inline double calc(int j1, int j2){
    	return (double) (dp[j2] + g[j2] - dp[j1] - g[j1]) / (f[j2] - f[j1]);
    }
    #undef int
    int main(){
    	cin >> N >> L;
    	for(int i = 1; i <= N; i++)
    	{
    		cin >> sum[i];
    		sum[i] += sum[i - 1];
    		f[i] = sum[i] + i;
    		g[i] = (f[i] + L + 1) * (f[i] + L + 1);
    	}
    	g[0] = (L + 1) * (L + 1); 
    	for(int i = 1; i <= N; i++){
    		while(h < t && calc(Q[h], Q[h + 1]) <= 2 * f[i]) h++;
    		dp[i] = dp[Q[h]] + (f[i] - f[Q[h]] - L - 1) * (f[i] - f[Q[h]] - L - 1); 
    		while(h < t && calc(Q[t], i) < calc(Q[t - 1], Q[t])) t--;
    		Q[++t] = i;
    
    	}
    	cout << dp[N];
    	return 0;
    }
    
  • P1912 [NOI2009] 诗人小G

    很显然的 dp 方程:

    fi=min(fj+|sumisumj+ij1L|P)

    其中

    sumx=i=1xai

    如果这个状态转移方程是决策单调的,那么可以直接上单调队列。

    但是怎么证明呢?

    我们只需证明函数Gj(i)=|sumi+i(sumj+j)(1+L)|P满足四边形不等式。

     Gj(i+1)+Gj+1(i)Gj(i)+Gj+1(i+1)

    尝试把左右两边统一化,简化式子,表示 Gj,Gj+1

    u=sumi+i(sumj+j)(1+L),v=sumi+i(sumj+a[j]+j+1)(1+L)

    |u+1+ai+1|P+|v|P|u|P+|v+1+ai+1|P

    |v|P|v+ai+1+1|P|u|P|u+ai+1+1|P

     u>v

    所以原问题等价于证明 h(x)=|x|P|x+z|P(z[0,)) 非严格单调递减。

    注意到有绝对值,我们分类讨论。

    x[0,):

    h(x)=xP(x+z)P

    h(x)=PxP1P(x+z)P1

    =PxP1Pi=0P1CP1ixPi1zi$$=Pi=1P1CP1ixPi1zi

    由于 z0,x[0,),h(x)0,Q.E.D.

    x(,0),P0(mod2):

    h(x)=xP(x+z)P

    证明过程同第一种情况,此处省略。

    x[z,0),P1(mod2):

    h(x)=xP(x+z)P

    h(x)=PxP1P(x+z)P1

    由于 xP1 恒大于 0h(x)0,Q.E.D.

    x(,z),P1(mod2):

    h(x)=xP+(x+z)P

    h(x)=PxP1+P(x+z)P1

    h(x)=P(xP1(x+z)P1)

    显然 xP1>(x+z)P1,因为这是一个偶函数。

    所以 h(x)0,Q.E.D.

    Code
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long double LD;
    const int N = 100010;
    
    int n, L, P;
    LD f[N];
    char str[N][31];
    int s[N], opt[N];
    struct Node
    {
    	int j, l, r;
    }q[N];
    int hh, tt;
    
    LD val(int j, int i)
    {
    	LD res = 1, a = abs(s[i] - s[j] + i - j - 1 - L);
    	for (int i = 0; i < P; i ++ ) res *= a;
    	return res + f[j];
    }
    
    void insert(int i)
    {
    	int pos = n + 1;
    	while (hh <= tt && val(q[tt].j, q[tt].l) >= val(i, q[tt].l)) pos = q[tt -- ].l;
    	if (hh <= tt && val(q[tt].j, q[tt].r) >= val(i, q[tt].r))
    	{
    		int l = q[tt].l, r = q[tt].r;
    		while (l < r)
    		{
    			int mid = l + r >> 1;
    			if (val(q[tt].j, mid) >= val(i, mid)) r = mid;
    			else l = mid + 1;
    		}
    		q[tt].r = r - 1;
    		pos = r;
    	}
    	if (pos != n + 1) q[ ++ tt] = {i, pos, n};
    }
    
    int main()
    {
    	int T;
    	scanf("%d", &T);
    	while (T -- )
    	{
    		scanf("%d%d%d", &n, &L, &P);
    		for (int i = n; i >= 1; i -- ) scanf("%s", str[i]);
    		for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + strlen(str[i]);
    		hh = tt = 0;
    		q[0] = {0, 1, n};
    		for (int i = 1; i <= n; i ++ )
    		{
    			f[i] = val(q[hh].j, i), opt[i] = q[hh].j;
    			if (q[hh].r == i) hh ++ ;
    			q[hh].l = i + 1;
    			insert(i);
    		}
    
    		if (f[n] > 1e18) puts("Too hard to arrange");
    		else
    		{
    			printf("%lld\n", (long long)f[n]);
    			for (int i = n; i; i = opt[i])
    			{
    				for (int j = i; j > opt[i]; j -- )
    				{
    					printf("%s", str[j]);
    					if (j != opt[i] + 1) printf(" ");
    				}
    				puts("");
    			}
    		}
    		puts("--------------------");
    	}
    
    	return 0;
    }
    
posted @   Star_F  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示