算法随笔——DP优化

单调队列优化DP

单调队列模板:

int head = 1,tail = 0;
	for (int i = 1;i <= n;i++)
	{
		while (head <= tail && head 不满足条件) head++;//踢出队列
		if (head <= tail) f[i] = f[q[head]] + ...;
		while (head <= tail && tail 与 i 不满足单调性) tail--;
		q[++tail] = i;
	}

优化思路则是对于类似于这样的 dp 式:

fi=maxiM<=j<=i1{fj+w}

在其中求区间最值时,因为其上下限均单调变化,因此可以用单调队列将转移优化到 O(1)

单调队列优化多重背包

单调队列优化多重背包

思路

首先我们列出 dp 式:

fj=max1cntsi{fjcntvi+cntwi}

我们考虑将第二维 V 按照除以 vi 的余数分组。

对于每个余数 u[0,vi1],倒序循环 p=(Vu)/vi
因此可以写出新的状态转移方程:

fu+pvi=maxpsikp1{fu+kvi+(pk)wi}

然后就可以用单调队列优化。
但我不会了,先鸽。

四边形不等式

整理自:oiwiki学习链接

四边形不等式即为:
如果对于任意 abcd 都有

w(a,c)+w(b,d)w(a,d)+w(b,c)

则称 w 满足四边形不等式。

决策单调性

opt[i] 为 dp_i 的转移点,若对于任意 i<j,都有 opt[i]<opt[j],则称该问题满足决策单调性。

定理

对于 fi=min1ji{fj+w(j,i)},若 w 满足四边形不等式,该问题具有决策单调性。

f 具有决策单调性时,我们可以使用分治优化时间至 O(NlogN)

分治优化

void solve(int l,int r,int lopt,int ropt,int id)
{
	if (l > r) return;
	if (lopt > ropt) return;
	
	int res = 0,mid = l + r >> 1;
	
	int k = -1;
	//枚举转移点
	for (int i = lopt;i <= min(ropt,mid);i++)
	{
		int tmp = calc(i + 1,mid);	
		if (f[id-1][i] + tmp > res) res = f[id-1][i]+tmp,k = i;
	}
	f[id][mid] = res; //计算 mid的值及转移点
	solve(l,mid-1,lopt,k,id); //分治
	solve(mid + 1,r,k,ropt,id);
}

例题:https://www.luogu.com.cn/problem/CF833B

需要一个类似莫队的东西 O(1) 移动指针计算贡献,时间复杂度是 O(nlogn)

线段树优化 DP

P1295 书架

P1295 [TJOI2011] 书架
一道线段树优化 DP 好题。

题意

给出一个长度为 n 的序列 h,请将 h 分成若干段,满足每段数字之和都不超过 m,最小化每段的最大值之和。

主要思路

朴素 DP 很好列:
fi 表示考虑前 i 个数的答案。
转移有:

fi=maxprei1ji1{fj+g[j+1][i]}

其中 g[l][r] 表示 h[l,r] 中的最大值。

我们发现需要求区间最值,考虑线段树优化转移。
线段树中维护 [1,i1]fj+g[j+1][i] 的最值。
想要维护这个值,需要再维护一个 g[j+1][i],便于修改。
考虑当前遍历到 i,会对 [premxi,i]g 产生贡献,将 g 变为 h[i],跑区间修改即可。
premxi 可以使用 st 表提前预处理出来。
于是本题就做完了。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define int ll
#define PII pair<int,int>
int read()
{
	int f=1,k=0;char c = getchar();
	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
	return k*f;
}

const int N = 1e5+5;

int n,m,h[N];
int pre[N],premx[N];
int f[N];


struct node
{
	int maxf,maxs,tag;
}tr[N<<2];

void pushdown(int k)
{
	if (tr[k].tag)
	{
		tr[k<<1].maxs = tr[k<<1].maxf + tr[k].tag;
		tr[k<<1|1].maxs = tr[k<<1|1].maxf + tr[k].tag;
		tr[k<<1].tag = max(tr[k<<1].tag,tr[k].tag);
		tr[k<<1|1].tag = max(tr[k<<1|1].tag,tr[k].tag);
		tr[k].tag = 0;
	}
}

void pushup(int k)
{
	tr[k].maxs = min(tr[k<<1].maxs,tr[k<<1|1].maxs);
	tr[k].maxf = min(tr[k<< 1].maxf,tr[k<<1|1].maxf);
}
void modifyf(int k,int l,int r,int x,int v)
{
	if ( x >r || x<l) return;
	if (l == r)
	{
		tr[k].maxs -= tr[k].maxf;
		tr[k].maxf = v;
		tr[k].maxs += v;
		return;
	}
	pushdown(k);
	int mid = l + r >> 1;
	modifyf(k << 1,l,mid,x,v);
	modifyf(k<<1|1,mid + 1,r,x,v);
	pushup(k);
}

void modifyg(int k,int l,int r,int x,int y,int v)
{
	if (x > r || y < l) return;
	if (x <= l && r <= y)
	{
		tr[k].maxs = tr[k].maxf + v;
		tr[k].tag = v;
		return;
	}
	pushdown(k);
	int mid = l +r >> 1;
	modifyg(k<<1,l,mid,x,y,v);
	modifyg(k<<1|1,mid + 1,r,x,y,v);
	pushup(k);
}
void print(int k,int l,int r)
{
	if (l == r)
	{
		cout << l<<" " << tr[k].maxf << ' ' << tr[k].maxs << endl;
		return ;
	}
	int mid = l +r >> 1;
	print(k<<1,l,mid);
	print(k<<1|1,mid + 1,r);
}
int query(int k,int l,int r,int x,int y)
{
	if (x > r|| y < l) return 1e18;
	if (x <= l && r <= y) return tr[k].maxs;
	int mid = l +r >> 1;
	pushdown(k);
	return min(query(k << 1,l,mid,x,y) , query(k<<1|1,mid + 1,r,x,y));
}

int dp[N][25];
void prework()
{
	for (int i = 1;i <= n;i++) dp[i][0] = h[i];
	for (int j = 1;j <= 20;j++)
		for (int i = 1;(i + (1<<j)-1) <= n;i++)
		{
			dp[i][j] = max(dp[i][j-1],dp[i + (1<<(j-1))][j-1]);
		}
}

int qmax(int l,int r)
{
	int k = log2(r-l+1);
	return max(dp[l][k],dp[r-(1<<k)+1][k]);
}

signed main()
{
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		h[i] = read();
	
	prework();
	int sum = 0;
	for (int i = n,j = n+1;i >= 1;sum-=h[i],i--)
	{
		while (sum+h[j-1] <= m && j > 1) sum += h[--j];
		pre[i] = j;
	}
	for (int i = 1;i <= n;i++)
	{
		int l = 1,r = i;
		while (l < r)
		{
			int mid = l +r >> 1;
			if (qmax(mid,i) > h[i]) l = mid + 1;
			else r = mid;
		}
		premx[i] = l;
	}
	f[0] = 0;
	f[1] = h[1];
	modifyf(1,1,n,1,f[1]);
	for (int i = 2;i <= n;i++)
	{
		modifyg(1,1,n,max(premx[i]-1,1ll),i-1,h[i]);
		f[i] = query(1,1,n,max(pre[i]-1,1ll),i-1ll);
		if (pre[i]== 1)
		{
			f[i] = min(f[i],qmax(1,i));
		}
		modifyf(1,1,n,i,f[i]);
	}
	cout << f[n] << endl;
	
	return 0;
}
posted @   codwarm  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示