2020.05.24日常总结——又一道特别好的 dp 题

25 篇文章 0 订阅
本文介绍了洛谷P1121题目,探讨如何在环状数组中找到连续非重叠的两段子段,使得它们的和最大。文章详细解释了破环为链的策略,包括复制数组和不复制数组的处理方法,并通过分类讨论解决了不复制数组时的问题。此外,还提供了针对不同情况的解决方案和代码实现。
摘要由CSDN通过智能技术生成

洛谷P1121     环状最大两段子段和 \color{green}{\texttt{洛谷P1121\ \ \ 环状最大两段子段和}} 洛谷P1121   环状最大两段子段和

【题意】: \color{blue}{\texttt{【题意】:}} 【题意】:

  • 给你一个首尾相接的数组 a a a(即环),选出其中连续 不重叠非空两段 使得这两段和最大。
  • 2 ≤ n ≤ 2 × 1 0 5 , − 1 × 1 0 4 ≤ a i ≤ 1 × 1 0 4 ( 1 ≤ i ≤ n ) 2 \leq n \leq 2 \times 10^5,-1\times 10^4 \leq a_i \leq 1 \times 10^4(1 \leq i \leq n) 2n2×105,1×104ai1×104(1in)
  • 注意两段子段可以相连(相当于一段)。

【思路】: \color{blue}{\texttt{【思路】:}} 【思路】:

对于环上的 dp,我们一般考虑破环为链法,即把环转化为链。

破环为链法中,又有几种比较常见的处理方法,这里列举两种:

  1. 把数组复制两遍,即把数组 a 1.. n a_{1..n} a1..n 变成数组 a 1..2 n a_{1..2n} a1..2n,其中 a i = a i − n ( n + 1 ⩽ i ⩽ 2 n ) a_i=a_{i-n}(n+1 \leqslant i\leqslant 2n) ai=ain(n+1i2n)。这是最常用的一种破环为链法。
  2. 不复制,直接用其它方法把环转化为链。

在本题中,我们发现虽然第一种很常用,但是我们仍然无法用它来解决问题。所以我们考虑第二种方法。

我们发现用第二种方法时,我们无法把所有的情况统一在一起讨论,这个时候,我们就需要用到另一种数学( OI \texttt{OI} OI 也适用)的方法了,那就是:分类讨论。

  • 第一种情况:两段子段都不跨越首尾,即类似于这样的:

    0001111100002220000 0001111100002220000 0001111100002220000

    0 0 0 表示该位不选, 1 1 1 表示选且属于第一子段, 2 2 2 表示选且属于第二子段。

    我们记 f i f_i fi 表示 a 1.. i a_{1..i} a1..i 的最大子段和, g i g_i gi 表示 a i . . n a_{i..n} ai..n 的最大子段和,则答案为:

    max ⁡ i = 1 n f i + g i + 1 \max\limits_{i=1}^{n} f_i+g_{i+1} i=1maxnfi+gi+1

  • 第二种情况:其中一个子段跨越了首尾,即类似于此:

    2220001111110000022 2220001111110000022 2220001111110000022

    命名方法同上。

    直接求很难,我们就考虑反着做,毕竟正难则反。

    我们发现 0 0 0 所占据的正好就是两个子段,于是,我们即 f i f_i fi 表示 a 1.. i a_{1..i} a1..i 的最小子段和, g i g_i gi 表示 a i + 1.. n a_{i+1..n} ai+1..n 的最小子段和。则答案为:

    ∑ i = 1 n a i − min ⁡ i = 1 n f i + g i + 1 \sum\limits_{i=1}^{n} a_i - \min\limits_{i=1}^{n} f_i + g_{i+1} i=1naii=1minnfi+gi+1

    注意当 f i + g i + 1 f_i+g_{i+1} fi+gi+1 正好就是整个区间的情况,需要特判,因为这样相当于选得数为空。

【代码】: \color{blue}{\texttt{【代码】:}} 【代码】:

const int N=2e5+100;
#define ll long long
int n;ll ans,res,sum;
ll f[N],g[N],h[N],a[N];
const ll inf=0x3f3f3f3f3f;
inline ll calc_min_sum(){
	h[0]=h[n+1]=f[0]=g[n+1]=inf;
	for(register int i=1;i<=n;i++){
		h[i]=min(h[i-1]+a[i],a[i]);
		f[i]=min(f[i-1],h[i]);
	}
	for(register int i=n;i>=1;i--){
		h[i]=min(h[i+1]+a[i],a[i]);
		g[i]=min(g[i+1],h[i]);
	}
	register ll ret=inf;
	for(int i=1;i<=n;i++)
		ret=min(ret,f[i]+g[i+1]);
	return ret;
}
inline ll calc_max_sum(){
	h[0]=h[n+1]=f[0]=g[n+1]=-inf;
	for(register int i=1;i<=n;i++){
		h[i]=max(h[i-1]+a[i],a[i]);
		f[i]=max(f[i-1],h[i]);
	}
	for(register int i=n;i>=1;i--){
		h[i]=max(h[i+1]+a[i],a[i]);
		g[i]=max(g[i+1],h[i]);
	}
	register ll ret=-inf;
	for(int i=1;i<=n;i++)
		ret=max(ret,f[i]+g[i+1]);
	return ret;
}
int main(){
	freopen("t1.in","r",stdin);
	n=read();//我们的程序开始了 
	for(int i=1;i<=n;i++)
		sum+=(a[i]=read());
	ans=calc_max_sum();//求最大 
	res=calc_min_sum();//求最小 
	if (res==sum){//全部数子为负 
		res=ans=-inf;//最大;次大 
		for(int i=1;i<=n;i++){
			if (a[i]>res){
				ans=res;
				res=a[i];
			}
			else ans=max(ans,a[i]);
		}
		printf("%lld",ans+res);
	}
	else{//数字有正有负(普通情况) 
		printf("%lld",max(ans,sum-res));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值