1. 程式人生 > 實用技巧 >洛谷題解P1115 最大子段和 暨 P1714 切蛋糕

洛谷題解P1115 最大子段和 暨 P1714 切蛋糕

P1115原題傳送門

P1714原題傳送門

\(\text{Solution - P1117}\)

一道 DP 題目。

  • 狀態的表示 : 令 \(f[i]\) 表示 以 \(f[i]\) 結尾的最大子段和。
  • 初始化 : \(f[i]\ =\ 0\)
  • 狀態的轉移 : \(f[i]\ =\ \max(f[i-1]+a[i],a[i])\)

但,對於狀態的轉移,我們可以發現數據範圍

對於 \(100\%\) 的資料,保證 \(1 \leq n \leq 2 \times 10^5\ ,\ -10^4 \leq a_i \leq 10^4\)

明確標註了數列中可能存在負數,這一點在測試點 \(2\) 中也得到了完美的體現。(話說全是負數)

當數列中存在負數時,\(f[n]\) 不一定是 \(\max\)

故,還需再在求完 \(f[i]\) 後求最大值並記錄答案。

\(\text{Code}\)

#include<iostream>
#include<cstdio>
using namespace std;
const int Maxn=2e5+10;
inline void read(int &x){
	int f=1;
	char ch=getchar();
	x=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	x*=f;
} 
int n;
int a[Maxn];
int dp[Maxn];
inline int max(int a,int b){return a>b?a:b;}
int main(){
	int ans=1<<31;      //int max-> 1<<31-1,1<<31<0
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i]);
		dp[i]=max(dp[i-1]+a[i],a[i]);
		ans=max(dp[i],ans);
	}
	printf("%d",ans);
	return 0;
}

\(\text{Solution - P1117 & P1714}\)

這兩道題都可以用一種資料結構來解決。(雙倍經驗)
這兩道題都是最大區間的題
所以可以維護一個字首和單調遞增的單調佇列。
基本和滑動視窗相似,只不過不需要維護一個 \(p\) 陣列來記錄佇列中元素的值。

\(\text{Code-P1714}\)\(\text{P1115}\) 程式碼相似,請讀者自行修改)

#include<iostream>
#include<cstdio>
using namespace std;
const int Maxn=5e5+10;
inline void read(int &x){
	int f=1;
	char ch=getchar();
	x=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<3)+(x<<1)+(ch&15);
		ch=getchar();
	}
	x*=f;
}
int n,m;
int p;
int sum[Maxn];
int q[Maxn];
int main(){
	read(n);read(m);
	for(int i=1;i<=n;i++){
		read(p);
		sum[i]=sum[i-1]+p;
	}
	int l=1,r=1;	//佇列中預設有元素了,所以 r=1  
	int ans=1<<31;	
	for(int i=1;i<=n;i++){
		while(l<=r&&q[l]<i-m) l++;	//過時出隊 
		ans=max(ans,sum[i]-sum[q[l]]);	//sum[i]-sum[q[l]] -> 通過維護字首和陣列求得當前區間和 
		while(l<=r&&sum[i]<=sum[q[r]]) r--;	//維護單調遞增 
		q[++r]=i;
	}
	printf("%d",ans);
	return 0;
}