决策单调性优化DP

决策单调性优化DP

何为决策单调性?

\(dp\) 转移时常会出现以下几种情形:(以 \(min\) 为例)

  1. \(dp[i]=min_{j<i}(dp[j]+w(j,i))\)

  2. \(dp[i][j]=min_{j<i}(dp[i-1][j]+w(j,i))\)

  3. \(dp[l][r]=min_{l\le k\le r}(dp[l][k]+dp[k+1][j])+w(i,j)\)

当前状态从哪里转移来,哪里就是最优决策点。(如最优决策点 \(j\) 是满足 \(dp[j]+w(j,i)\) 最小的 \(j\) )

决策单调性顾名思义就是最优决策点的移动是有规律的。

以第一种情形来说就是 \(i\) 的最优决策点一定小于等于 \(i+1\) 的最优决策点。

那么借助这个性质就可以优化 \(dp\)

决策单调性的判定条件:

四边形不等式:

对于代价函数 \(w(l,r)\) ,若对于任意 \(a\le b\le c \le d\) ,满足:

\[w(a,c)+w(b,d)\le w(a,d)+w(b,c) \]

则称 \(w\) 满足四边形不等式(交叉小于包含)。

据此我们可以证明情形一二的决策单调性:

设 \(dp[i]\) 的最优决策点为 \(j\) ,然后随便指定一个点 \(k\) 使得 \(k< j\) ,已知 \(dp[k]+w(k,i)\ge dp[j]+w(j,i)\)

那么:

\(dp[i+1]\) 从 \(dp[k]\) 转移的代价为 \(dp[k]+w(k,i+1)\) ,从 \(dp[j]\) 转移的代价是 \(dp[j]+w(j,i+1)\) 。

因为 \(dp[k]+w(k,i)\ge dp[j]+w(j,i)\),所以 \(dp[k]-dp[j]\ge w(j,i)-w(k,i)\)

根据决策单调性,\(w(j,i)-w(k,i)\le w(j,i+1)-w(k,i+1)\)

所以 \(dp[k]-dp[j]\ge w(j,i+1)-w(k,i+1)\)

移项后就得到了 \(dp[k]+w(k,i+1)\ge dp[j]+w(j,i+1)\)

故最优决策点一定单调不降。

如果 \(w(l,r)\) 还额外满足 \(w(b,c)\le w(a,d)\) ,那么就能推出区间 \(dp\) 的决策单调性(即情形三):

先证明 \(dp\) 数组也满足四边形不等式(用归纳法,这里省略,具体见[DP满足四边形不等式证明](DP满足四边形不等式证明 - wlhao - 博客园))。

证明 \(opt[l][r - 1] \leq opt[l][r]\)

记 \(p = opt[l][r - 1]\),即 \(p\) 是区间 \([l, r - 1]\) 的最优分割点。

我们需证明:

\(dp[l][p] + dp[p + 1][r] \leq dp[l][k] + dp[k + 1][r]\) ( \(k < p\) )

(注意两边都少了相同的 \(w(l, r)\),所以比较时可以省略。)

因为 \(p\) 是 \([l, r - 1]\) 的最优分割点,所以对于所有 \(k < p\),有:

\(dp[l][p] + dp[p + 1][r - 1] \leq dp[l][k] + dp[k + 1][r - 1]\) (1)

由于 \(k + 1 \leq p + 1 \leq r - 1 \leq r\)

应用四边形不等式到 \(a = k + 1, b = p + 1, c = r - 1, d = r\):

\(dp[k + 1][r - 1] + dp[p + 1][r] \leq dp[k + 1][r] + dp[p + 1][r - 1]\) (2)

将 (1) 和 (2) 相加:

左边相加:

\(dp[l][p] + dp[p + 1][r - 1] + dp[k + 1][r - 1] + dp[p + 1][r]\)

右边相加:

\(dp[l][k] + dp[k + 1][r - 1] + dp[k + 1][r] + dp[p + 1][r - 1]\)

注意到两边都有 \(dp[p + 1][r - 1]\) 和 \(dp[k + 1][r - 1]\),它们会抵消。相加后得到:

\(dp[l][p] + dp[p + 1][r] \leq dp[l][k] + dp[k + 1][r]\)

因此对于任何 \(k < p\),用 \(p\) 分割 \([l, r]\) 的代价都不大于用 \(k\) 分割的代价。

所以最优分割点 \(opt[l][r]\) 不可能小于 \(p\),即 \(opt[l][r] \geq opt[l][r - 1]\)。这证明了左不等式。

右不等式同理。

怎么优化DP?

乍一看好像直接双指针优化就行了,因为每一步并没有保证从 \(1-i\) 是一个单峰函数。

也就是说可能指针 \(p\) 发现 \(p+1\) 的值比 \(p\) 要大,就不走了。但实际最小值可能是在 \(p+2\) 的位置。

那怎么做呢,这里分情况讨论:

第一种情况:\(dp[i]=min_{j<i}(dp[j]+w(j,i))\)

维护一些决策三元组(恰好覆盖 \([1-n]\) ),形如 \((j,l,r)\) 表示 \(j\) 这个决策点在 \([l,r]\) 是最优的。

最初是的决策三元组是 \((0,1,n)\)

处理 \(dp[i]\) 时从队首开始看,如果当前队首的决策三元组的区间包含 \(i\) (即 \(r\ge i\) ),那么就可以直接从这个决策点转移,否则就直接弹出队首。

然后考虑用 \(dp[i]\) 去更新决策三元组,也就是我们想知道哪一段区间的最优决策点能更新为 \(i\) 。(下文中管辖的定义:若某区间的最优决策点都是 \(i\) ,则称 \(i\) 管辖这段区间)

从队尾往前看:

首先,队尾的决策三元组的最优决策点一定是小于 \(i\) 的。

其次,根据决策单调性,越靠后的点的决策最优点越有可能是 \(i\) 。

因此大致想到去二分答案,但是现在二分的区间(\([i+1-n]\))可能被很多个决策三元组切碎了。

但是发现只需要判断某个决策三元组区间起点 \(l\) 从 \(j\) 转移和从 \(i\) 转移哪个更优,就能判断这个区间应不应该全部划给 \(i\) 管辖。(决策单调性: \(l\) 的决策点中 \(i\) 优于 \(j\) ,后面的那 \(i\) 就更优于 \(j\) )

先把队尾所有被 \(i\) 管辖的区间弹出(如下图中删除区间 \([r+1,n]\)),然后在不能完全划给 \(i\) 管辖的区间里面二分出一个分界线,那么分界线后面就都归 \(i\) 管辖。

如图所示,假设区间 \([l+1,r]\) 被 \(j\) 管辖。

那么二分一个 \(mid\) ,使得 \(mid\) 从 \(i\) 转移更优,而 \(mid-1\) 从 \(j\) 转移更优。

所以删除三元组 \((j,l,r)\)

然后加入 \((j,l,mid-1)\) 以及 \((i,mid,n)\)

第二种情况:\(dp[i][j]=min_{j<i}(dp[i-1][j]+w(j,i))\)

当前层只依赖于上一层的结果,那么可以分批处理。

对于第 \(i\) 层的 \(dp[l...r]\),已知最优决策范围为 \([optL,optR]\)

取中点 \(mid\) ,那么暴力遍历 \([optL,optR]\) 可以找到 \(mid\) 的的最优决策点 \(optM\)。

那么 \(dp[l...mid]\) 的最优决策范围为 \([optL,optM]\) 。

\(dp[mid+1...r]\) 的最优决策范围为 \([optM+1,optR]\) 。

故可以分治递归处理。

这样总时间复杂度从 \(O(n^3)\) 优化到了 \(O(n^2log\,n)\)

第三种情况:\(dp[l][r]=min_{l\le k\le r}(dp[l][k]+dp[k+1][j])+w(i,j)\)

设 \(opt[l][r]\) 为区间 \([l,r]\) 的最优决策点。那么根据决策单调性,有 \(opt[l][r-1]\le opt[l][r]\le opt[l+1][r]\)

即:区间向左扩张,最优化决策点不会右移,当向右扩张时最优决策点不会左移。

那么据此我们就可以只枚举 \(k\in [opt[l][r-1],opt[l+1][r]]\)

时间复杂度分析:

固定长度 \(len = r-l+1\),该长度下所有区间的枚举总量为:

\[S_{len} = \sum_{l=1}^{n-len+1} \Big( opt[l+1][l+len-1] - opt[l][l+len-2] + 1 \Big) \]

展开后中间项相互抵消,得到:

\[S_{len} = opt[n-len+2][n] - opt[1][len-1] + (n-len+1) \]

由于 \(opt\) 取值在合法范围内,有上界 \(n-1\) 和下界 \(1\),所以:

\[S_{len} \le 2n - len - 1 = O(n) \]

总枚举次数:

\[\sum_{len=1}^{n} S_{len} \le \sum_{len=1}^{n} O(n) = O(n^2) \]

综上所述,整体复杂度降为 \(O(n^2)\)。

例一:P1912 诗人小G

第一种情况的板子。

注意决策单调性中不能溢出截断,这样会导致决策错误,如:

\(dp[a]=200,dp[b]=50\) 而从两者转移的 \(cost\) 都大于 \(1e18\) ,那么这样会判 \(b\) 更优,但实际上从 \(dp[a]\) 转移的贡献是小于从 \(dp[b]\) 转移的贡献,这样就导致最优决策点永久的跳过了 \(a\) ,导致答案错误。

所以用 \(long\ double\) ,不能溢出截断。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int N=2e5+10;
const ll INF=1000000000000000000LL+10;
int T,n,L,P;
long double dp[N];          // dp[i]:前 i 个句子排版的最小不协调度
string s[N];
int sum[N],pre[N];          // sum:句子长度前缀和;pre:最优转移点
// 快速幂,long double 不截断,保留真实大小关系
long double pw(long double a,int b){
    long double r=1;
    while(b){
        if(b&1)r*=a;
        a*=a;b>>=1;
    }
    return r;
}
// 从 j 转移到 i 的代价:把 j+1..i 放一行
long double cost(int j,int i){
    long double d=abs((long double)i-j+sum[i]-sum[j]-1-L);
    return pw(d,P);
}
// 在位置 i,决策 a 是否不劣于决策 b
bool better(int a,int b,int i){
    return dp[a]+cost(a,i)<=dp[b]+cost(b,i);
}
struct Node{int j,l,r;};    // 决策 j 负责的最优区间 [l,r]
deque<Node>q;
// 二分找决策 now 开始优于队尾决策的位置
int split(int now){
    int l=q.back().l,r=n,ans=n+1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(better(now,q.back().j,mid)){ans=mid;r=mid-1;}
        else l=mid+1;
    }
    return ans;
}
// 插入新决策 now,维护单调队列
void insert(int now){
    // 若 now 在队尾决策的起点就更优,直接弹掉队尾
    while(!q.empty()&&better(now,q.back().j,q.back().l))q.pop_back();
    if(q.empty()){q.push_back({now,now+1,n});return;}
    int pos=split(now);
    if(pos<=n){
        q.back().r=pos-1;           // 队尾决策只负责到 pos-1
        q.push_back({now,pos,n});   // now 负责 pos..n
    }
}
string ans[N];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>T;
    while(T--){
        cin >> n >> L >> P;
        sum[0]=0;
        for(int i=1;i<=n;i++){
            cin >> s[i];
            sum[i]=sum[i-1]+(int)s[i].length();
            dp[i]=1e100;                // 初始化极大值
        }
        dp[0]=0;
        q.clear();
        q.push_back({0,1,n});           // 初始决策 0
        for(int i=1;i<=n;i++){
            // 1. 弹掉已过期的队首
            while(!q.empty()&&q.front().r<i)q.pop_front();
            // 2. 取队首作为最优决策
            int j=q.front().j;
            pre[i]=j;
            // 3. 计算 dp[i]
            dp[i]=dp[j]+cost(j,i);
            // 4. 插入新决策 i
            insert(i);
        }
        // 最小不协调度超过 1e18 则输出 Too hard
        if(dp[n]>INF-10){
            cout<<"Too hard to arrange\n--------------------\n";
            continue;
        }
        cout << (ll)(dp[n]+0.5) << '\n';    // 四舍五入输出整数
        // 回溯路径,构造每行
        int cnt=0;
        for(int cur=n;cur;cur=pre[cur]){
            cnt++;
            ans[cnt]="";
            for(int k=pre[cur]+1;k<=cur;k++){
                ans[cnt]+=s[k];
                if(k!=cur)ans[cnt]+=" ";
            }
        }
        for(int i=cnt;i>=1;i--){
            cout << ans[i] << '\n';
        }
        cout << "--------------------\n";
    }
    return 0;
}

例二:P3515 Lighting Conductor

有 \(n\) 栋建筑排成一列,高度为 \(h_i\)​。若在建筑 \(i\) 顶部建高 \(p\) 的避雷针,则能保护建筑 \(j\) 当且仅当

\[h_j​≤h_i​+p−\sqrt{∣i−j∣​} \]

对每个 \(i\),求能保护所有建筑的最小非负整数避雷针高度 \(p_i\)​。输出 \(n\) 行答案。

转换:

对于一个点而言:

设 \(p1\) 表示一个点保护所有在它前面的点的最小高度 ,\(p2\) 表示保护它后面所有点的最小高度

则该点的答案等于 \(max(p1,p2)\) 。

也就是说我们应该先正着做一遍,再反着做一遍。

方法:( \(p1\) 为例)

\(i\) 点的答案其实就是 \(max(h_j+\sqrt{|i-j|})-h_i\)

那么我们猜测这个式子是有决策单调性的。

证明如下:

我们要证明 \(h_j-h_i+\sqrt{|i-j|}\) 满足反向四边形不等式 。

设 \(a\le b \le c \le d\) ,

\(w(a,c)=h_a-h_c+\sqrt{c-a}\)

\(w(b,d)=h_b-h_d+\sqrt{d-b}\)

\(w(a,d)=h_a-h_d+\sqrt{d-a}\)

\(w(b,c)=h_b-h_c+\sqrt{c-b}\)

那么:

\(w(a,c)+w(b,d)=h_a+h_b-h_c-h_d+\sqrt{c-a}+\sqrt{d-b}\)

\(w(a,d)+w(b,c)=h_a+h_b-h_c-h_d+\sqrt{d-a}+\sqrt{c-b}\)

所以要证明 $w(a,c)+w(b,d)\ge w(a,d)+w(b,c) $ 只需要证明 \(\sqrt{c-a}+\sqrt{d-b}\ge\sqrt{d-a}+\sqrt{c-b}\)

因为左右两边都大于等于0,所以左右乘平方,得到:

\(c+d-a-b+\sqrt{cd+ab-bc-ad}\) 和 \(c+d-a-b+\sqrt{cd+ab-ac-bd}\)

所以只需要证明:\(\sqrt{cd+ab-bc-ad}\ge\sqrt{cd+ab-ac-bd}\)

也就是证明:\(bc+ad\le ac+bd\)

由于 \(a(d-c)\le b(d-c)\),

所以 \(bc+ad\le ac+bd\) 得证,证明结束。

那么这道题就做完了。

P4767 邮局

数轴上有 \(n\) 个村庄,你可以在村庄上建立 \(m\) 个邮局,求所有村庄和离其最近的邮局之间距离的最小可能总和。

\(1\le m\le n\le 3000,1\le m\le 300, 1\le 村庄位置\le 10000\)

如何 \(DP\) ?

设 \(dp[i][j]\) 表示建了 \(i\) 个邮局,且第 \(i\) 个建在第 \(j\) 个村庄的最小距离总和。

那么显然有转移:

\[dp[i][j]=min_{k<j}(dp[i-1][k]+w(k,j)) \]

\(w(i,j)=将位置大于\ (i+j)/2\ 的村庄的最近距离从到\ i\ 改变到\ j\ 的贡献\)

这是类型二,仍然猜测 \(w(k,j)\) 满足四边形不等式。

具体证明看这个吧,决策单调性证明

实际考试时主要是打表去发现,应该没人会去硬推(除非很容易看出来)。

那么就做完了啊!

代码也是非常好写:顺便提一嘴,下面的代码没有要求邮局只能建在村庄上,但过了。

#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
const int N=1e4+10;
int V,P;
int pos[N];
int cnt[N],sum[N];
int max_pos=0;
int dp[310][N];
int trans(int l,int r){
    int mid=(l+r)>>1;
    int now_cnt=cnt[r]-cnt[mid];
    int now_sum=sum[r]-sum[mid];
    int now_cnt2=cnt[max_pos]-cnt[r];
    int now_sum2=sum[max_pos]-sum[r];
    return (now_cnt*r-now_sum)-(now_sum-now_cnt*l)+now_sum2-now_cnt2*r-(now_sum2-now_cnt2*l);
}
void solve(int id,int l,int r,int optl,int optr){
    if (optl>optr) return;
    if (l>r) return;
    int mid=(l+r)>>1;
    int optid=optl;
    for (int k=optl;k<=min(optr,mid);k++){
        int v=dp[id-1][k]+trans(k,mid);
        if (dp[id][mid]>v){
            optid=k;
            dp[id][mid]=v;
        }
    }
    if (l==r) return;
    solve(id,l,mid-1,optl,optid);
    solve(id,mid+1,r,max(optid,optl),optr);
}
signed main(){
    cin >> V >> P;
    for (int i=1;i<=V;i++){
        cin >> pos[i];
        max_pos=max(max_pos,pos[i]);
        cnt[pos[i]]++;
    }
    for (int i=1;i<=max_pos;i++){
        sum[i]=sum[i-1]+cnt[i]*i;
        cnt[i]+=cnt[i-1];
    }
    memset(dp,0x3f,sizeof dp);
    for (int i=1;i<=max_pos;i++){
        dp[1][i]=0;
        dp[1][i]+=cnt[i]*i-sum[i];
        dp[1][i]+=sum[max_pos]-sum[i]-(cnt[max_pos]-cnt[i])*i;
    }
    for (int i=2;i<=P;i++) solve(i,1,max_pos,1,max_pos);
    int minn=0x3f3f3f3f;
    for (int i=1;i<=max_pos;i++) minn=min(dp[P][i],minn);
    cout << minn;
    return 0;
}


例三:P5504

把序列分成若干连续段,每段选一个其中出现过的颜色 \(x\),若出现 \(t\) 次,得分 \(x t^2\),求最大总得分。

解:

由于某一段只能选一个数,那么在使得这个数的得分不变的前提下,显然我们让序列越短越好。

那么在 \(DP\) 中就意味着当处理到 \(i\) 时,我们只用从 \(dp[j-1](col[j]=col[i])\) 进行转移,且这一段选定的颜色一定是 \(col[i]\) 。

贡献函数 \(w(i,j)=(cnt[i]-cnt[j-1])^2\cdot col[i]\) 满足四边形不等式,证明:

(留空一整页)

设 \(a \le b \le c \le d\),对应颜色均为 \(C\)(\(C > 0\))。记前缀次数 \(S_i = cnt[i]\)。

代入展开:
\(w(a, c) + w(b, d) = C \cdot (S_c - S_{a-1})^2 + C \cdot (S_d - S_{b-1})^2\)
\(w(a, d) + w(b, c) = C \cdot (S_d - S_{a-1})^2 + C \cdot (S_c - S_{b-1})^2\)

即证:\((S_c - S_{a-1})^2 + (S_d - S_{b-1})^2 \le (S_d - S_{a-1})^2 + (S_c - S_{b-1})^2\)

左式减右式得:
\(= -2S_cS_{a-1} - 2S_dS_{b-1} + 2S_dS_{a-1} + 2S_cS_{b-1}\)
\(= 2(S_{a-1} - S_{b-1})(S_d - S_c)\)

由于 \(a \le b \implies S_{a-1} \le S_{b-1}\),且 \(c \le d \implies S_c \le S_d\)
所以 \((S_{a-1} - S_{b-1}) \le 0\),\((S_d - S_c) \ge 0\)。
乘积 \(\le 0\)。

即 \(w(a, c) + w(b, d) \le w(a, d) + w(b, c)\)。

这与我们之前见过的不同。

因为这里是取 \(max\) ,然而又满足四边形不等式。也就是说决策点单调不增。

何为决策点单调不增?

你可能会说,决策点单调不增那决策点不就一直为 \(0\) 了吗?(这个问题DeepSeek都说不清楚)

决策点单调不增是指如果当前决策点 \(b\) 不如 \(a\) 且( \(a<b\) ) ,那么以后也不会更优,所以 \(b\) 没用了。

但是对于 \(b\) 优于 \(a\) ,我们不能断定 \(a\) 没用了。

那么点 \(i\) 的决策点 \(opt[i]\) 有:\(opt[i]\le opt[i-1]或opt[i]=i-1\) 。具体视情况而定。

这是为什么呢,对于已经考虑过的决策点,那确实都已成定局,但对于未考虑的决策点 \(i-1\) ,我们并不能直接断定说 \(i-1\) 不如 \(opt[i-1]\) 优秀。所以就是说还得去考虑决策点 \(i-1\) 。

所以其实本质就是一个单调栈的过程:每次先把 \(i-1\) 加到栈顶,然后去看栈顶是不是比次栈顶优,如果不是就直接弹出,重复执行后剩下的栈顶就是最优的。

P5897 wombats NB!

题意简述

有一个 \(N\) 行 \(M\) 列的网格图,\(N\) 很大,\(M\) 很小。
每条水平边、竖直边都有权值。需要支持三种操作:

  1. 修改某条水平边的权值;
  2. 修改某条竖直边的权值;
  3. 查询从第 \(1\) 行第 \(x\) 列走到第 \(N\) 行第 \(y\) 列的最短路长度。

核心思路

1. 按行分治,线段树维护 \(DP\)

因为行数 \(N\) 很大,列数 \(M\) 很小,所以考虑把“行”放到线段树上维护。

线段树每个节点维护一段连续的行区间 \([l,r]\),并保存一个 \(M\times M\) 的 \(DP\) 矩阵:

\[f_{i,j}=\text{从第 }l\text{ 行第 }i\text{ 列走到第 }r+1\text{ 行第 }j\text{ 列的最短路} \]

这里故意走到 \(r+1\) 行,也就是“跨过下边界”,这样合并两个子区间时可以直接相加,不用再额外加竖直边。

合并左右儿子 \(A,B\) 时:

\[C_{i,j}=\min_{k=1}^{M}\big(A_{i,k}+B_{k,j}\big) \]

其中 \(k\) 表示从中间某一列穿过。


2. 决策单调性优化合并

直接枚举 \(k\) 合并是 \(O(M^3)\) 的,需要优化。

注意到最优路径不会交叉:如果两条最优路径交叉,一定可以交换后半段得到不劣的解。如图:

假设红线是 \(i\) 到 \(j\) 的最佳线路,显然蓝线不如绿线,不然红线就应该走蓝线的后半段了。所以路线不会交叉,因此转移满足决策单调性。

设 \(p_{i,j}\) 为 \(C_{i,j}\) 的最优决策点 \(k\),则有:

\[p_{i-1,j}\le p_{i,j}\le p_{i,j+1} \]

所以枚举 \(k\) 时只需要在:

\[[\,p_{i-1,j},\ p_{i,j+1}\,] \]

内枚举即可。如第三类情况,这样每次合并的复杂度降为 \(O(M^2)\)。


3. 分块优化空间

如果直接对每一行建线段树,每个节点存 \(M^2\) 的矩阵,空间是:

\[O(NM^2) \]

当 \(N=5000,M=200\) 时约为 \(2\times 10^8\) 个 \(int\),空间爆炸。

于是采用“分块 + 线段树”的思想:

  • 把连续的行分成若干块,每块大小约为 \(S\);
  • 线段树的叶子节点不是一个单行,而是一个行块;
  • 块内直接暴力 \(DP\) 求出该块的 \(M\times M\) 矩阵;
  • 线段树内部仍然按上面的方式合并。

这样线段树叶子数降为 \(O(N/S)\),空间降为:

\[O\left(\frac{N}{S}M^2+NM\right) \]

实际取 \(S=20\sim 25\) 比较合适。


复杂度分析

设块大小为 \(S\),修改次数为 \(T\),查询次数为 \(Q\)。

  • 建树:
    每个块内暴力 DP 是 \(O(SM^2)\),共 \(N/S\) 个块,所以块内总复杂度 \(O(NM^2)\)。
    线段树合并总复杂度 \(O((N/S)M^2)\)。
    因此建树总复杂度约为 \(O(NM^2)\)。

  • 单次修改:
    重算所在块 \(O(SM^2)\),线段树向上更新 \(O(M^2\log(N/S))\)。
    所以单次修改复杂度为:

    \[O\big(M^2(S+\log(N/S))\big) \]

  • 单次查询:
    直接输出根节点矩阵的值,\(O(1)\)。

  • 空间复杂度:

    \[O\left(\frac{N}{S}M^2+NM\right) \]


注意事项

  1. 矩阵 \(f_{i,j}\) 的含义是“跨过下边界”的最短路,所以叶子块计算时会多走一行到 \(d+1\)。对于最后一个块,第 \(n+1\) 行不存在,但对应的 \(v2[n][j]\) 默认为 \(0\),不会影响答案。
  2. 决策单调性优化中,枚举范围是 \([p_{i-1,j},p_{i,j+1}]\),注意边界处理。
  3. 块大小 \(S\) 可以取 \(20\sim 25\),平衡时间和空间。
  4. 修改后只需重算该行所在的块,然后在线段树上向上合并即可。
  5. 查询直接取根节点矩阵的 \(f_{x,y}\),其中 \(x,y\) 是起点列和终点列。
posted @ 2026-09-06 16:46  wlhao  阅读(24)  评论(0)    收藏  举报