环形与后效性处理

环形结构上的动态规划问题

在许多环形结构问题中,我们都能通过枚举法,选择一个位置把环断开,变成线性结构进行计算,最后根据每次枚举的结果求出答案。我们把能用上述枚举方式求解的环形问题称为“可拆解的环形问题”,这也是本节的主要研究对象。我们的目标是采取适当策略避免枚举,从而降低时间复杂度。

通常来说,我们解决环形问题的方式有两种:

  • 执行两次DP

  • 破环为链

下面我们将结合例题分别介绍这两种方法

执行两次DP

例题: P6064 Naptime G

先来考虑一个“简化版”:假设第 N 个小时与次日第一个小时不是相连的,那么这就是一个明显的DP题:

dpi,j,0/1 来表示在第 i 个小时,已经休息了 j 个小时,0 表示这个小时没在休息,1 表示这个小时正在休息。

状态转移方程就是:

dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+u[i]);

初值:dp1,1,1=0dp1,0,0=0 其余为 -INF。

答案:max(dpn,b,0,dpn,b,1)


但问题是第 N 个小时与次日第一个小时是相连的,上面的方法不能直接用。既然这样,那就再来一次强制连接:

强制让第 N 个小时睡觉,让次日第一个小时熟睡。

状态转移方程如上。

初值:dp1,1,1=u1dp1,0,0=0,其余为-INF。

答案:dpn,b,1

具体来说,我们可以先将第 N 个小时与次日第一个小时断开,然后用上面的“简化版”的方法来得到一个答案。再强制让第 N 个小时睡觉,让次日第一个小时熟睡,用上面讨论的强制连接的方法得到第二个答案,两者取最大值即可。

ps:此题可用滚动数组优化空间

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,b,ans=0;
int f[2][3835][2],u[3835];
signed main(){
	cin>>n>>b;
	memset(f,-0x3f,sizeof(f));
	f[1][0][0]=f[1][1][1]=0;
	for(int i=1;i<=n;i++) cin>>u[i];
	for(int i=2;i<=n;i++){
		f[i&1][0][0]=0;
		for(int j=1;j<=i;j++){
			f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
			f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+u[i]);
		}
	}
	ans=max(f[n&1][b][0],f[n&1][b][1]);
	memset(f,-0x3f,sizeof(f));
	f[1][1][1]=u[1];f[1][0][0]=0;
	for(int i=2;i<=n;i++){
		f[i&1][0][0]=0;
		for(int j=1;j<=i;j++){
			f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
			f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+u[i]);
		}
	}
	ans=max(ans,f[n&1][b][1]);
	cout<<ans;
	return 0;
}

破环为链

例题: 环路运输

题目描述

环上有 N 个点,每个点有点权 Ai,相邻仓库距离为 1,任意两点 i,j 之间的距离定义为沿环的两侧分别从 i 前往 j 的距离中的较小值,即 disi,j=min(|ij|,N|ij|)。定义任意两点 i,j 之间运输货物的代价为 Ai+Aj+disi,j,求在环上哪两点间运输货物的代价最大。1N106

解法

断环为链的思想可以简述为:对于一个长度为 N 的环,我们不妨将其断开并延长一倍,使之变成一条长度为 2N 的链,并将其划分为若干部分分别考虑。

我们在任意位置(例如仓库 1N 之间)把环断开,复制一倍接在末尾,形成长
度为 2N 的直线公路。在转化之后的模型中,公路旁均匀分布着 2N 座仓库,其中 Ai=Ai+N(1iN)

对于原来环形公路上的任意两座仓库 ij (1j<iN),如果 ijN/2,那么在新的直线公路上,仍然可以对应成在 ij 之间运送货物,代价为Ai+Aj+ij

如果 ij>N/2,那么可以对应成在 ij+N 之间运送货物,代价为 Ai+Aj+N+j+Ni,其中 j+Ni=N(ij)N/2

综上所述,原问题可以等价转化为:长度为 2N 的直线公路上,在满足 1j<i2N 并且 ijN/2 的哪两座仓库 ij 之间运送货物,运送代价Ai+Aj+ij最大?

我们可以枚举 i,对于每个 i,需要找到一个 j[iN/2,i1],使 Ajj 尽量大。在“最大子序和”这道例题的解答中,我们已经探讨过同样的问题。使用单调队列进行维护,可以在均摊 O(1) 的时间内找到这样的 j。整个算法的时间复杂度为 O(N)

#include <bits/stdc++.h>

using namespace std;
int n,ans;
int a[2000002]; 
deque<int> q;
int main() {
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	q.push_back(1);
	for(int i=2;i<=2*n;i++){
		while(q.size()&&i-q.front()>n/2){
			q.pop_front();
		}
		int j=q.front();
		ans=max(ans,a[i]+i+a[j]-j);
		while(q.size()&&a[q.back()]-q.back()<=a[i]-i){
			q.pop_back();
		}
		q.push_back(i);
	}
	cout<<ans;
	return 0;
}

有后效性的状态转移方程

从最初学习DP开始,我们就多次强调,“阶段”是动态规划的三要素之一,“无后效性”是应用动态规划算法的三前提之一。事实上,在一些题目中,当我们根据题目的关键点抽象出“状态维度”,并设计出状态表示和状态转移方程后,却发现这道形似DP的题目不满足“无后效性”这一基本条件——部分状态之间互相转移、互相影响,构成了环形,无法确定出一个合适的DP“阶段”,从而沿着某个方向执行递推。

事实上,我们可以把动态规划的各状态看作未知量,状态的转移看作若干个方程。如果仅仅是“无后效性”这一条前提不能满足,并且状态转移方程都是一次方程,那么我们可以 不进行线性递推,而是用高斯消元直接求出状态转移方程的解

在更多的题目中,动态规划的状态转移“分阶段带环”——我们需要把DP和高斯消元相结合,在整体层面采用动态规划框架,而在局部使用高斯消元解出互相影响的状态。我们用一道例题来具体说明这类情况。

posted @   「ycw123」  阅读(155)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示