1. 程式人生 > 實用技巧 >序列 [樹狀陣列+離散化]

序列 [樹狀陣列+離散化]

序列

題目描述

給定兩個長度為n的序列 \(a, b\) 。你需要選擇一個區間\([l,r]\),使得 \(a_l+…+a_r\geqslant 0\)\(b_l+…+b_r\geqslant 0\)。最大化你選擇的區間長度。

輸入格式

第一行一個整數 \(n\),第二行 \(n\) 個整數 \(a_1\to a_n\),第三行 \(n\) 個整數 \(b_1\to b_n\)

輸出格式

一行一個整數表示\(max(r-l+1)\)。保證至少有一個區間滿足條件。

樣例

樣例輸入

5
2 -4 1 2 -2
-2 3 1 -3 1

樣例輸出

1

資料範圍與提示

對於 \(20\%\) 的資料,\(n\leqslant 5000\)

對於 \(60\%\) 的資料,\(n\leqslant 10^5\)

對於 \(100\%\) 的資料,\(1\leqslant n\leqslant 10^6,|a_i|, |b_i|\leqslant 10^9\)。 資料有一定梯度。

分析

看到題目就能得到三個柿子,也就是個三維偏序:

\[suma_r-suma_l\geqslant 0\\ sumb_r-sumb_l\geqslant 0\\ r-l\geqslant 0\]

但是三個不等式是非常難以維護的,所以我們考慮簡化一下這三個式子。

我們可以首先讓一個條件滿足,我們先按 \(suma\) 的大小升序排序,這樣第一個式子就絕對滿足了,我們只需要考慮剩下的二維偏序問題。

因為 \(sumb\) 的範圍肯定要比 \(long\ long\) 還大,所以我們需要離散化來進行儲存。因為我們需要滿足在 \(sumb\) 的條件滿足的情況下,找到當前這個位置向左拓展的最小的左邊界,這個區間的長度就是答案。

根據上邊所說的,我們就可以使用樹狀陣列來維護每個 \(sumb\) 的最小左端點,用 \(sumb\) 當作下標(離散化之後的),用當前這個值的位置來更新值,在查詢的時候查詢這個位置即可,答案就是當前值的位置減去查詢到的左端點,取 \(max\) 即可。

程式碼



#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
ll suma[maxn],sumb[maxn];
struct Node{
	ll a,b,id;
}jl[maxn];
ll n;
inline ll read(){
	ll s = 0,f = 1;
	char ch = getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch))s=s*10+ch-'0',ch=getchar();
	return s*f;
}
int t[maxn];
int lowbit(int x){
	return x & -x;
}
void modify(int x,int val){//修改最小值
	while(x <= n){
		t[x] = min(t[x],val);
		x += lowbit(x);
	}
}
int query(int x){//查詢最小左端點
	int res = 0x3f3f3f3f;
	while(x){
		res = min(res,t[x]);
		x -= lowbit(x);
	}
	return res;
}
bool cmp(Node x,Node y){//按suma排序
	return x.a < y.a;
}
int main(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	memset(t,0x3f,sizeof(t));//因為要維護最小的左端點,所以初始化極大值
	n = read();
	for(register int i = 1;i <= n;++i){
		ll x = read();
		jl[i].id = i;
		suma[i] = suma[i-1] + x;
		jl[i].a = suma[i];
	}
	for(register int i = 1;i <= n;++i){
		ll x = read();
		sumb[i] = sumb[i-1] + x;
		jl[i].b = sumb[i];
	}
	int ans = 1;
	for(register int i = 1;i <= n;++i){
		if(sumb[i] >= 0 && suma[i] >= 0){//先提前處理一下,不然會掛
			ans = max(ans,i);
		}
	}
	sort(jl+1,jl+n+1,cmp);//按suma排序,去掉一個限制條件
	//以下兩行離散化
	sort(sumb+1,sumb+n+1);
	int q = unique(sumb+1,sumb+n+1)-sumb-1;
	for(int i=1;i<=n;++i){
		int pos = lower_bound(sumb+1,sumb+q+1,jl[i].b) - sumb;//找到這個sumb的位置(也就是離散化後的值)
		modify(pos,jl[i].id);//修改
		ans = max((ll)ans,jl[i].id - query(pos));//查詢並更新答案
	}
	printf("%d\n",ans);
	return 0;
}