决策单调性优化DP+分治优化决策单调性
前言
本来这篇已经写了\(\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;
}