2019牛客暑期多校訓練營(第二場)----J-Subarray

首先發出題目鏈接:
鏈接:https://ac.nowcoder.com/acm/contest/882/J
來源:牛客網
涉及:分治

點擊這裏回到2019牛客暑期多校訓練營解題—目錄貼


題目如下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
數組的長度已經被定下來了,爲10910^9,此時告訴我們肯定不能直接使用前綴和。

但是還有一個條件非常有用:那就是1的個數不多,而且我們知道每一段1的位置及長度。

那麼對於其他所有的-1,有一些-1是有用的(與其他的1或-1成爲一個區間使得區間和仍然大於0),有一些-1則完全沒有用,那些有用的-1和1組成的連續區間我們叫它有用區間。如圖:
在這裏插入圖片描述

於是可以完全不考慮那些沒用的-1,直接考慮所有的1和所有有用的-1,這樣我們需要考慮的區間長度最多隻有10710^7的長度。

還有一件事,原數組默認從0開始,然後我在輸入的時候就全部加1,打算從1開始

r[0]=l[0]=0,r[n+1]=l[n+1]=1e9+1;
for(i=1;i<=n;i++){
	int a,b;
	scanf("%d%d",&a,&b);
	l[i]=a+1,r[i]=b+1;//全部加1,讓數組從下標爲1開始
}

對於每一段連續的1,都可以找出一段有用區間,有些有用區間可能重合或者相鄰,那麼這兩段有用區間可以合成爲一段有用區間。如圖:
在這裏插入圖片描述
上圖中對於兩段全1區間,它們所對應的有用區間重合了,於是這整個一段合併成爲一段有用區間。

如下圖仍然是兩段有用區間:
在這裏插入圖片描述
先將所有連續全1區間的左邊界位置和右邊界位置分別放在兩個數組llrr

先創建兩個數組frontfrontbackback,其中
 front[i]front[i]表示以第i段全1區間的右邊界爲右邊界的最大區間和。
 back[i]back[i]表示以第i段全1區間的左邊界爲左邊界的最大區間和。
那麼front和back數組滿足兩個遞推式:
front[i]=r[i]l[i]+1+max(front[i1](l[i]r[i1]1),0)front[i]=r[i]-l[i]+1+max(front[i-1]-(l[i]-r[i-1]-1),0)back[j]=r[j]l[j]+1+max(back[j+1](l[j+1]r[j]1),0)back[j]=r[j]-l[j]+1+max(back[j+1]-(l[j+1]-r[j]-1),0)

解釋一下:假設以第i段全1區間的右邊界爲右邊界的區間最大和爲n(front[i]=n)(表示front[i]=n),第i段全1區間和第i+1段中間的-1數量有m(l[j+1]r[j]1=m)(表示l[j+1]-r[j]-1=m)個。
如果m&gt;nm&gt;n,那麼以第i+1段全1區間的右邊界爲右邊界的區間最大和front[i+1](表示front[i+1])只能爲r[i+1]l[i+1]+1r[i+1]-l[i+1]+1(表示第i+1段全1區間的1的數量);
如果mnm\le n,那麼front[i+1]=r[i+1]l[i+1]+1+nmfront[i+1]=r[i+1]-l[i+1]+1+n-m

back[n+1]=front[0]=0;
for(i=1,j=n;i<=n && j>=1;i++,j--){
	front[i]=r[i]-l[i]+1+max(front[i-1]-(l[i]-r[i-1]-1),0);//從頭到尾更新front的值
	back[j]=r[j]-l[j]+1+max(back[j+1]-(l[j+1]-r[j]-1),0);//從尾到頭更新back的值
}

如果第i段有用區間和第i+1段有用區間能合併,那麼滿足back[i+1]+front[i]l[i+1]r[i]1back[i+1]+front[i] \ge l[i+1]-r[i]-1

可以參照下圖理解(圖中沒有標出3個全1區間的有用區間):
在這裏插入圖片描述
可以判斷第i個有用區間往後可以合併多少有用區間,用一個數pospos來表示,故

int pos=i;//此時遍歷到第i個有用區間
while(pos<=n && front[pos]+back[pos+1]>=l[pos+1]-r[pos]-1)	pos++;//判斷往後能不能繼續合併後面的有用區間

合併完畢,我們就得到了真正意義上的每一段有用區間,對每一段有用區間進行求解,然後將每一段求解的答案加起來就是答案

合併之後的有用區間我們不知道他的左邊界和右邊界的位置,它其實是類似於下面這樣的區間:
在這裏插入圖片描述
這一段合併之後的區間我們還要看它往左和往右能延伸到什麼位置,分別存到變量firstfirstlastlast中,假設這一段合併區間跨過了第i段到第pos段的全1區間,那麼:
first=max(l[i]back[i],1)first=max(l[i]-back[i],1)last=min((int)1e9,r[pos]+front[pos])last=min((int)1e9,r[pos]+front[pos])

解釋一下:由於這一段合併區間跨過了第i段到第pos段的全1區間,於是l[i]就是合併區間中第一段全1區間的左邊界,r[pos]就是合併區間中最後一段全1區間的右邊界;
如果以l[i]爲左邊界的最大區間和爲back[i],那麼l[i]往左最多可以容納back[i]個-1;
如果以r[pos]爲右邊界的最大區間和爲front[pos],那麼r[i]往右最多可以容納front[pos]個-1;

於是這一段合併區間真正的邊界就被找出來了:
在這裏插入圖片描述

第一步對於這一段合併區間肯定是求前綴和,然後需要判斷有多少對正序對即可(參考逆序對),可以像求解逆序對一樣用樹狀數組,但是會超時。
求合併區間前綴和代碼:

		int t=i;//此合併區間第i個全1區間開始,t表示下一個未遍歷到的全1區間的序號
		for(int k=first;k<=last;k++){//k從區間開始遍歷到區間結尾,下面那個sum數組是合併區間的前綴和數組
			if(k>=l[t] && k<=r[t])	sum[k-first+1]=sum[k-first]+1;//遍歷到某一段全1區間內,前綴和就加1
			else	sum[k-first+1]=sum[k-first]-1;//遍歷到-1區間內,前綴和就加減1
			if(k==r[t])	t++;//此全1區間全部遍歷完畢,t++表示該遍歷第t+1個全1區間了
			cnt[sum[k-first+1]+base]=0;//這個先不管	
		}

但是我們知道由於原合併區間中只有1或者-1,所以在前綴和sumsum數組中任意兩個相鄰的數相差1

觀察上方那個合併區間的前綴數組
在這裏插入圖片描述

代碼如下:

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int maxm=1e7+5;
int n;
int l[maxn],r[maxn],back[maxn],front[maxn];
int cnt[3*maxm],num1=0,num2=0; 
int sum[3*maxm]={0};
ll ans=0;
int main(){
	scanf("%d",&n);
	int i,j;
	back[n+1]=front[0]=r[0]=l[0]=0,r[n+1]=l[n+1]=1e9+1;
	for(i=1;i<=n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		l[i]=a+1,r[i]=b+1;
	}
	for(i=1,j=n;i<=n && j>=1;i++,j--){
		front[i]=r[i]-l[i]+1+max(front[i-1]-(l[i]-r[i-1]-1),0);
		back[j]=r[j]-l[j]+1+max(back[j+1]-(l[j+1]-r[j]-1),0);
	}
	i=1;
	int base=1e7+1;
	while(i<=n){
		int pos=i,t=i;
		while(pos<=n && front[pos]+back[pos+1]>=l[pos+1]-r[pos]-1)	pos++;
		ll first=(ll)max(l[i]-back[i],1),last=(ll)min((int)1e9,r[pos]+front[pos]);
		for(int k=first;k<=last;k++){
			if(k>=l[t] && k<=r[t])	sum[k-first+1]=sum[k-first]+1;
			else	sum[k-first+1]=sum[k-first]-1;
			if(k==r[t])	t++;
			cnt[sum[k-first+1]+base]=0;	
		}
		cnt[0+base]=1;
		for(int k=1;k<=last-first+1;k++){
			if(sum[k]>sum[k-1])	num2=num1+cnt[sum[k-1]+base];
			else	num2=num1-cnt[sum[k]+base];
			cnt[sum[k]+base]++;
			ans+=num2;
			swap(num1,num2);
		}
		i=pos+1;
	}
	printf("%lld",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章