序列 方法記錄

2022-10-14 21:13:02

序列

題目背景

題目描述

作為一名火星人,你為了佔領地球,需要想方設法使地球人失去信心。現在你獲得了一項能力,控制今後 \(n\) 天的天氣溫度,對於第 \(i\) 天,你能將溫度控制在 \([a_i,b_i]\) 中任意一個數位,你的目的是使其中某段時間,溫度持續不下降,趁此來攻擊地球。

現在問你最多可以使連續的多少天滿足溫度不下降。

輸入格式

第一行給出一個整數 \(n\),表示你能控制的天數。

接下來 \(n\) 行,第 \(i\) 行給出 \(2\) 個整數 \(a_i,b_i\),表示你能控制的天氣範圍。保證 \(a_i\le b_i\)

輸出格式

輸出一個整數,表示答案。

樣例 #1

樣例輸入 #1

4
1 3
2 4
1 1
3 4

樣例輸出 #1

2

提示

對於 \(20\%\) 的資料 \(3\le n\le10\)

對於 \(40\%\) 的資料 \(3\le n\le3000\)

對於 \(60\%\) 的資料 \(3\le n\le100000\)

對於 \(100\%\) 的資料 \(3\le n\le1000000\)\(1<=a_i,b_i<=100000\)

題解

通過對題意的理解,不難想出\(O(n^2)\)的暴力做法。

列舉從每一天開始,能得到的最大溫度不下降天數。

對於列舉的每一天,我們可以用貪心的思想來使溫度不下降天數儘可能大。

\(i\)天的溫度下限、上限分別為\(a[i]\)\(b[i]\).要使溫度不下降天數儘可能大,那麼每一天的溫度就應該在控制能力範圍內儘可能小。記錄一個第\((i-1)\)天的最小溫度\(tem\)\(tem\)的初值為\(0\)),那麼對於第\(i\)天,若\(b[i]>=tem\)則說明你有能力使溫度不下降,這裡就可以對答案作出貢獻了。在此基礎上,若\(a[i]>=tem\),則將\(a[i]\)的值賦給\(tem\)來使\(tem\)在允許的前提下儘可能小。最終的答案就是以每一天為起點最大不下降天數的最大值。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1000005;
int n,ans,tem,tmp;
int a[N],b[N];
int maxx(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i],&b[i]);
	for(int l=1;l<=n;l++)
	{
		tmp=0;
		tem=0;
		for(int i=l;i<=n;i++)
		{
			if(b[i]>=tem)
			{
				tmp++;
				if(a[i]>=tem) tem=a[i];
			}
			else break;
		}
		ans=maxx(ans,tmp);
	}
	printf("%d\n",ans);
	return 0;
}

考慮正解

回憶一下我們暴力做法外層迴圈做的工作,可以概括為「從第\(i\)天開始有多少天能維持不下降」,這一步體現的單調性不禁讓人想起單調佇列

可以看看我寫的這篇單調佇列的模板題。

不如讓我們先來重溫一下滑動視窗吧。

下面的程式碼塊可以處理出視窗最小值:

for(int i=1;i<=n;i++)//視窗最小值 
{
	while(h<=t&&i-q1[h]>=k) h++; 
	while(h<=t&&a[i]<a[q1[t]]) t--;
	q1[++t]=i;
	if(i>=k) printf("%d ",a[q1[h]]);
}

一行一行地加以理解。

while(h<=t&&i-q1[h]>=k) h++; 

實現視窗移動。

while(h<=t&&a[i]<a[q1[t]]) t--;

這是對已經入隊的老成員的「考驗」,這一步決定了老成員的去或留。

q1[++t]=i;

這是新成員入隊的「機會」。有趣的是,新成員的機會往往伴隨著對老成員的考驗,或者說新成員入隊本身就是對老成員的考驗。一旦有老成員在「有生之年」都不如某即將入隊的新成員,那老成員就該出隊了。

再回到這道題。

程式設計上來講,最外層迴圈還是用於列舉每一天,但內層我們可以使用單調佇列來處理溫度不下降的天數。

單調佇列的核心,就是確定「機會」與「考驗」。

下面我們用\(l\)來表示開始的天數\(r\)來表示嘗試能夠維持溫度不下降的下一天。由於單調佇列的單調性,上文的\(tem\)就是\(a[q[h]]\),即隊首元素\(q[h]\)\(a\)陣列的下標。(順便一提,單調佇列中的\(q\)陣列一般都存的是位置資訊而不會存值)

判斷能否入隊。若\(b[r]>=a[q[h]]\),則有新隊員可以入隊;並且若老成員(依次列舉隊尾)的大小比不上新一天的最小溫度,即\(a[q[t]]<a[r]\),則出隊。下面就是核心程式碼了:

while(r<=n&&b[r]>=a[q[h]])
{
	while(h<=t&&a[q[t]]<a[r]) t--;//老成員出隊 
	q[++t]=r++;//新成員入隊 
}

另外本題還有一些細節需要處理。

如,需確保隊首元素大於等於\(l\),因為我們分析的是第\(l\)天及以後的日子。

while(h<=t&&q[h]<l) h++;

又如,當\(r<=l\)時,說明經歷了一次維持溫度不下降失敗,需要從失敗的位置開始再次嘗試之後能維持的天數。

if(r<=l)
{
	r=l+1;
	q[++t]=l;
}

AC程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm> 
using namespace std;
const int N=1000001;
int n,ans;
int a[N],b[N],q[N];
int maxx(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i],&b[i]);
	q[1]=1;
	int h=0,t=0;
	for(int l=1,r=2;l<=n&&r<=n;l++)//從l開始有多少天能維持不下降 
	{
		while(h<=t&&q[h]<l) h++;
		if(r<=l)
		{
			r=l+1;
			q[++t]=l;
		}
		while(r<=n&&b[r]>=a[q[h]])
		{
			while(h<=t&&a[q[t]]<a[r]) t--;//老成員出隊 
			q[++t]=r++;//新成員入隊 
		}
		ans=maxx(ans,r-l);
	}
	printf("%d\n",ans);
	return 0;
}

參考