决策单调性优化DP总结

oi-wiki的讲解

1D1D

转移代价函数满足交叉优于包含,即可推出具有决策单调性。

然后比较一般的做法是,根据每个点能够作为被决策点的是一段区间,且区间随着点连续右移,可以用单调队列维护决策点,每次加入时弹出队尾所有能够完全覆盖的,然后到第一个不完全覆盖的,二分找出区间左端点。

对于被决策点不会成为决策点的情况,也可以用分治解决,但单调队列+二分的做法最为普适。
值得注意的是,如果查询的代价函数需要O(log)的计算,那分治对比单调队列二分(需要比较两个决策点,且查询比起分治更不连续)能减小一半以上的常数,建议用分治(详见第三题)。

2D1D(区间类)

要求转移代价函数满足区间包含单调性+交叉优于包含
详见第二题。

两道做过的思路类似的题

ICPC2018NJ B

因为恰好选k个这个限制,肯定没法在DP内直接做(如果不增加一维状态);所以考虑wqs二分:对于选取的个数k来说,答案是随k增大而减小的;而如果给选取的每个点加上一个代价x,那么k会随着x增大减小,即答案随着x递增,我们要求的是满足k为给定的数且答案最小,那么二分x即可。
二分完之后,列出朴素DP式,发现代价满足四边形不等式,用普通的单调队列+二分优化DP即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
const ll F=1e15;
int n,k;
ll a[N],s[N];
ll f[N]; int g[N];
struct node{
	int i,l;
}q[N];
ll cnt(int l,int r){
	int mid=(l+r)>>1;
	return a[mid]*(mid-l+1)-(s[mid]-s[l-1])+(s[r]-s[mid])-a[mid]*(r-mid);
}
int dp(ll x){
	//cout<<"x="<<x<<endl;
	int ft=0,tl=0;
	q[++tl]=(node){0,1};
	for(int i=1;i<=n;i++){
		if(ft+1<tl && q[ft+2].l<=i) ft++;
		int d=q[ft+1].i;
		f[i]=f[d]+cnt(d+1,i)+x;
		g[i]=d;
	//	cout<<i<<" "<<f[i]<<" "<<g[i]<<endl;
		while(ft<tl){
			node u=q[tl];
			if(u.l>i && f[i]+cnt(i+1,u.l)<=f[u.i]+cnt(u.i+1,u.l)){
				tl--; continue;
			}
			int l=max(u.l,i+1),r=n;
			while(l<r){
				int mid=(l+r)>>1;
				if(f[i]+cnt(i+1,mid)<=f[u.i]+cnt(u.i+1,mid)) r=mid;
				else l=mid+1;
			}
			if(l<=n && f[i]+cnt(i+1,l)<=f[u.i]+cnt(u.i+1,l)) q[++tl]=(node){i,l};
			break;
		}
	}
	int c=0,u=n;
	while(u) c++,u=g[u];
	return c-1;
}
int main()
{
	//srand(time(0));
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	//int T; cin>>T; while(T--) work();
	cin>>n>>k;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
	ll l=0,r=F;
	while(l<r){
		ll mid=(l+r+1)>>1;
		if(dp(mid)>=k) l=mid;
		else r=mid-1;
	}
//	cout<<"l="<<l<<endl;
	dp(l);
	cout<<f[n]-l*k<<endl;
	return 0;
}

2022暑假牛客多校A

上一题的hard版,利用了DP状态的一些优美性质。

原本看到恰好选k个,直接的反应就是wqs二分,然后就不会了。

沿着wqs二分的思路,理论上也是能做的(确实有人卡过去了orz)
对于二维平面上n个点的凸包选取一个子集,代价为选取的点中每相邻两个点的距离,求最大代价的问题:容易想到floyd求最长路以及对应的DP形式f[l][r]=max{f[l][k]+w[k][r]},然而是O(n3)

这时候考虑决策单调性,发现代价w满足四边形不等式,所以就具有决策单调性。

而通常的决策单调性优化DP是用单调队列+二分,可以把一个n优化为logn;在本题中,因为DP状态是区间的形式,可以直接利用p[l][r1]<=p[l][r]<=p[l+1][r]这个性质,按照区间长度枚举,每个区间长度对应的范围之和为n,就把DP优化到O(n2)了!

但直接用wqs二分容易被卡精(原本被卡到自闭),又注意到此题的DP状态可以写成矩阵的形式,而要求恰好k步就是k个转移矩阵相乘!那么直接矩阵快速幂即可,容易发现在矩阵相乘的时候也是满足决策单调性的,优化之后即可做到O(n2logk)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
const double F=1e9;
int n,k;
double x[N],y[N],d[N][N];
struct mtx{
	double a[N][N],mx;
}p,s,t,q;
void init(mtx& A){
	for(int i=1;i<=n;i++){
		A.a[i][i]=0;
		for(int j=i+1;j<=n;j++) A.a[i][j]=-F;
	}
	A.mx=0;
}
double cnt(int l,int r,int k){
	return t.a[l][k]+q.a[k][r];
}
int ps[N][N];
void mul(mtx A,mtx B,mtx& C){
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			t.a[i][j]=A.a[i][j],q.a[i][j]=B.a[i][j];
	init(C);
	for(int len=1;len<=n;len++) for(int l=1;l+len-1<=n;l++){
		if(len==1){
			//make names clear!!!
			C.a[l][l]=0;
			ps[l][l]=l;
			continue;
		}
		int r=l+len-1;
		for(int j=ps[l][r-1];j<=ps[l+1][r];j++){
			double x=cnt(l,r,j);
			//cout<<j<<" "<<x<<endl;
			if(x>C.a[l][r]){
				C.a[l][r]=x;
				ps[l][r]=j;
			}
		}
		//printf("l=%d r=%d f=%lf g=%d p=%d\n",l,r,f[l][r],g[l][r],p[l][r]);
	}
	for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) C.mx=max(C.mx,C.a[i][j]+d[i][j]);
}
int main()
{
	//srand(time(0));
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	//int T; cin>>T; while(T--) work();
	cin>>n>>k;
	for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
	for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++)
		d[i][j]=p.a[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
	k--;
	for(;k;k>>=1,mul(p,p,p)) if(k&1) mul(s,p,s);
	printf("%.10lf\n",s.mx);
	return 0;
}

2023CCPC网络赛L

非DP问题的决策单调性。
首先按照b升序,考虑每个前缀i,对于每个k的贡献就是bi+ka
对于每个k,考虑快速找到使其最优的决策点i:可以发现随着k增加,i必然不降;利用这个单调性即可分治求解,查询用主席树,效率O(nlog2n)(单调队列易被卡常)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+5,F=1e9+5;
const ll inf=1e18;
int n;
namespace seg{
    struct{
        ll x,s;
        int l,r;
    }a[N<<5];
    int his[N],cnt,l0,r0;
    void init(int l,int r){
        l0=l,r0=r;
        cnt=0;
    }
    void upd(int& u,int x,ll k,int l=l0,int r=r0){
        a[++cnt]=a[u]; u=cnt;
        if(l==r){
            a[u].x++;
            a[u].s+=k;
            return;
        }
        int m=(l+r)/2;
        if(x<=m) upd(a[u].l,x,k,l,m);
        else upd(a[u].r,x,k,m+1,r);
        a[u].x=a[a[u].l].x+a[a[u].r].x;
        a[u].s=a[a[u].l].s+a[a[u].r].s;
    }
    //ll qry(int u,int x,int y,int l=l0,int r=r0)
    ll kth(int u,ll k,int l=l0,int r=r0){
        if(l==r) return min(a[u].x,k)*l;
        int m=(l+r)/2;
        ll lv=a[a[u].l].x;
        if(k<=lv) return kth(a[u].l,k,l,m);
        else return a[a[u].l].s+kth(a[u].r,k-lv,m+1,r);
    }
}using namespace seg;
struct node{
    int a,b;
}p[N];
bool cmp(node u,node v){
    return u.b<v.b;
}
ll ans[N];
void div(int l,int r,int x,int y){
    if(l>r) return;
    if(l==r){
        ll mn=inf;
        int pos=0;
        for(int i=max(x,l);i<=y;i++){
            ll u=kth(his[i],l)+p[i].b;
            if(u<mn) mn=u,pos=i;
        }
        ans[l]=mn;
        return;
    }
    int mid=(l+r)/2;
   // cout<<"l="<<l<<" r="<<r<<" mid="<<mid<<endl;
    ll mn=inf;
    int pos=0;
    for(int i=max(x,mid);i<=y;i++){
        ll u=kth(his[i],mid)+p[i].b;
        if(u<mn) mn=u,pos=i;
    }
    ans[mid]=mn;
   // cout<<"mn="<<mn<<" pos="<<pos<<endl;
    div(l,mid-1,x,pos); div(mid+1,r,pos,y);
}
void work(){
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d%d",&p[i].a,&p[i].b);
    sort(p+1,p+n+1,cmp);
    init(1,F);
    for(int i=1;i<=n;i++){
        his[i]=his[i-1];
        upd(his[i],p[i].a,p[i].a);
    }
    div(1,n,1,n);
    for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
}
int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    work();
    return 0;
}
posted @   sz[sz]  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示