前言
本来这篇已经写了\(\frac{2}{3}\)了 然后我关机时忘保存了。。。
华丽的分割线
决策单调性优化\(DP\)
对于类似于
\[dp[i][j]=max/min(dp[k - 1][j - 1] + count(k,i))\]
不妨设 当 最后一次 \(max/min\)更新时
\[f(i,j)=k\]
若有
\[\forall i,j\in[1,n],s.t. i < j \Rightarrow f(i,k)<=f(j,k)\]
我就可以称她具有决策单调性
例题
题意我就不概括了
据题 容易推出状态方程
\[dp[i]=min(dp[j-1]+count(i,j))\]
凭感觉 是具有决策单调性的
其实可以证明
不过我太菜了 不会
既然决策具有单调性
那么对于每一个决策点 我们可以拿出一个决策区间
用一个双端队列维护 决策点 和 决策区间
在每一次循环前
把区间\(.r<i\)的舍去
然后再以当前点为决策点看是否能比队列中的对后面的贡献更小
既
while(l <= r&&dp[i] + count(q[r].l,i) <= dp[q[r].pos] + count(q[r].l,q[r].pos)) r--;
注意\(while\)结束后还要特判一下
\[dp[i] + count(q[r].l,i) <= dp[q[r].pos] + count(q[r].l,q[r].pos)\]
不见得
\(\forall x \in[q[r].l,q[r].r]\Rightarrow dp[i] + count(x,i) > dp[q[r].pos] + count(x,q[r].pos)\)
所以还要二分找一下那个特殊的位置
因为决策点具有单调性\(\Rightarrow\)决策区间具有单调性\(\Rightarrow\)维护的双端队列具有单调性
与单调队列类似 每点只进入和删除一次
但是有二分
时间复杂度\(O(nlogn)\)
\(Code\)
#include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define reg register int #define isdigit(x) ('0' <= x&&x <= '9') template<typename T> inline T Read(T Type) { T x = 0,f = 1; char a = getchar(); while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();} while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();} return x * f; } typedef long long ll; const int MAXN = 50010; int n,L,a[MAXN]; ll dp[MAXN],sum[MAXN]; struct node { int pos,l,r; void ass(int Pos,int L,int R) {pos = Pos,l = L,r = R;} }q[MAXN]; inline ll co(ll x) {return x * x;} inline ll count(int i,int j) {return co(sum[i] - sum[j] + i - j - 1 - L);} inline int get_(int x,node seq) { int l = seq.l,r = seq.r; while(l <= r) { int mid = l + r >> 1; if(dp[x] + count(mid,x) <= dp[seq.pos] + count(mid,seq.pos)) { if(r == mid) return r; r = mid - 1; } else l = mid + 1; } return l; } int main() { n = Read(1),L = Read(1); for(reg i = 1;i <= n;i++) sum[i] = (a[i] = Read(1)) + sum[i - 1]; memset(dp,0x7f7f7f,sizeof(dp)); dp[0] = 0; int l = 1,r = 0; q[++r].ass(0,1,n); for(reg i = 1;i <= n;i++) { while(q[l].r < i) l++; dp[i] = dp[q[l].pos] + count(i,q[l].pos); q[l].l = i + 1; while(l <= r&&dp[i] + count(q[r].l,i) <= dp[q[r].pos] + count(q[r].l,q[r].pos)) r--; int pos = get_(i,q[r]); q[r].r = pos - 1; if(pos <= n) q[++r].ass(i,pos,n); } printf("%lld\n",dp[n]); return 0; }
这类\(DP\) 通常还能再优化
也许是用斜率 或单调队列
但是决策单调性的好想,好实现及其优于暴力的特点让我们常常使用
分治优化决策单调性
因为 决策具有单调性
那么就可以使用分治优化
#include <cstdio> #include <string> #include <iostream> #include <algorithm> using namespace std; #define reg register int #define isdigit(x) ('0' <= x&&x <= '9') template<typename T> inline T Read(T Type) { T x = 0,f = 1; char a = getchar(); while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();} while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();} return x * f; } const int MAXN = 4010,inf = 1000000000; int sum[MAXN][MAXN],dp[MAXN][810]; inline int count(int i,int j) {return sum[i][i] - sum[i][j - 1] - sum[j - 1][i] + sum[j - 1][j - 1];} inline void dfs(int k,int l,int r,int opl,int opr) { if(l > r) return; int mid = l + r >> 1; int minl = inf,id; for(int i = opl;i <= min(opr,mid);i++) { int cur = dp[i - 1][k - 1] + count(mid,i); if(cur < minl) minl = cur,id = i; } dp[mid][k] = minl; dfs(k,l,mid - 1,opl,id); dfs(k,mid + 1,r,id,opr); } int main() { int n = Read(1),k = Read(1); for(reg i = 1;i <= n;i++) for(reg j = 1;j <= n;j++) { int v = Read(1); sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + v; } for(reg i = 1;i <= n;i++) dp[i][0] = inf; for(reg i = 1;i <= k;i++) dfs(i,1,n,1,n); printf("%d",dp[n][k] / 2); return 0; }