算法随笔——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 式:

\[\huge {f_i = \max _{i-M <= j <= i-1}\{ f_j + w \}} \]

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

单调队列优化多重背包

单调队列优化多重背包

思路

首先我们列出 dp 式:

\[\huge{f_j = \max_{1 \le cnt \le s _ i} \{ f _ j-cnt*v_i + cnt * w_i \}} \]

我们考虑将第二维 \(V\) 按照除以 \(v_i\) 的余数分组。

对于每个余数 \(u \in [0,v_i-1]\),倒序循环 \(p = (V-u)/v_i\)
因此可以写出新的状态转移方程:

\[\huge{f_{u + p * v_i} = \max _{p-s_i \le k \le p-1} \{ f_{u + k * v _ i} + (p-k)*w _ i \}} \]

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

四边形不等式

整理自:oiwiki学习链接

四边形不等式即为:
如果对于任意 \(a \le b \le c \le d\) 都有

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

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

决策单调性

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

定理

对于 \(f_i = \min _{1 \le j \le i} \{ f_j + w(j,i) \}\),若 \(w\) 满足四边形不等式,该问题具有决策单调性。

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

分治优化

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(n \log n)\)
指针移动次数不超过 \(n\log n\)

线段树优化 DP

P1295 书架

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

题意

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

主要思路

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

\[\large {f_i = \max_{pre_i-1 \le j \le i-1} \{f_j + g[j+1][i] \} } \]

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

我们发现需要求区间最值,考虑线段树优化转移。
线段树中维护 \([1,i-1]\) 的 $f_j + g[j+1][i] $ 的最值。
想要维护这个值,需要再维护一个 \(g[j+1][i]\),便于修改。
考虑当前遍历到 i,会对 \([premx_i,i]\)\(g\) 产生贡献,将 \(g\) 变为 \(h[i]\),跑区间修改即可。
\(premx_i\) 可以使用 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;
}

斜率优化

https://vjudge.net/contest/734500#problem/J

#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>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define adde(a,b) v[a].push_back(b)
#define addev(a,b,c) v[a].push_back({b,c});
#define rd read
#define all(a) a.begin(),a.end()
#define mem(a,b) memset(a,b,sizeof a);
#define pb push_back
#define vct vector
#define rev(T) reverse(T.begin(),T.end())
#define endl "\n"
#define db long double

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 = 2e5+5;

int n,m,p,d[N],h[N],t[N];
int sumd[N];

int tot;
int a[N];
int cnt[N];
int f[N][105];
int q[N],tt,hh,S[N];

int nowp;

int X(int x){return x;}
int Y(int x){return f[x][nowp-1] +S[x]; }
int K(int x){return a[x];}
db slope(int a,int b)
{
	if (X(a) == X(b)) return (Y(a)>=Y(b))?INF:-INF;
	return (Y(a)-Y(b))/(X(a)-X(b));
}


void solvemain()
{
	cin >> n >> m >> p;
	rep(i,1,n-1) cin >> d[i],sumd[i+1] = sumd[i] + d[i];
	rep(i,1,m) cin >> h[i] >> t[i],t[i] -= sumd[h[i]];
	
	sort(t + 1,t + m + 1);
	
	n = m;
	for (int i = 1;i <= n;i++) a[i] = t[i],S[i] = S[i-1] + a[i];
	
	memset(f,INF,sizeof f);
	f[0][0] = 0;
	// for (int i = 1;i <= n;i++) cout << a[i] << ' ';puts("");
	for (int k =1;k <= p;k++)
	{
		nowp = k;
		hh = 1,tt = 0;
		f[0][k] = 0;
		q[++tt] = 0; //记得将 push 0
		for (int i = 1;i <= n;i++)
		{
			while (hh < tt && slope(q[hh],q[hh+1]) <= K(i)) hh++;
			f[i][k] = Y(q[hh]) - K(i) * X(q[hh]) - S[i] + a[i]*i;
			while (hh < tt && slope(q[tt-1],q[tt]) >= slope(q[tt],i)) tt--;
			q[++tt] = i;	
		}
	}
	
	cout << f[n][p] << endl;
	
	
	
	
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int t;t = 1;
	while(t--)
	{
		solvemain();
	}
	return 0;
}

记录一些dp优化的trick

把式子简化 简化到一个好看点的形式

尝试改变dp方法

考虑每次dp中重复部分

同时需要考虑求答案的部分

尝试简化每一部分

>

posted @ 2024-07-21 17:45  codwarm  阅读(42)  评论(0)    收藏  举报