SDU程序設計思維與實踐作業Week5

A 最大矩形

題目

題目
Input&&Output:
Input&&Output
Sample:

#Input:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

#Output:
8
4000

題解

1.根據題目,我們發現每個矩形條對應的最大矩形,就是它左右可以延伸到的最遠位置
而最遠位置就是高度(y值)大於等於它,因此我們發現對於每個矩形條我們實際上是
找它左邊第一個大於等於它的位置以及右邊最後一個大於等於它的位置。
2.我們直接將每個矩形條看作它最大的矩形,那麼我們會發現它右邊界一定大於它
相鄰的右邊第一個矩形(不然就不是最大的,邊界特判  也就是說我們只要能找到右
邊第一個比它小的值就可以了),我們可以認爲,矩形高度滿足一定的遞增規律,我
們只要記錄這組遞增的值對應的下標,就可以判斷出每個矩形的右邊界。
3.因此本題我們利用一種特別的數據結構 單調棧(出棧遞增或遞減)
4.由剛剛的分析 我們發現右邊界做一遍遞增棧,而右邊界再做一次遞減棧就可以了
5.但是實際上我們做一遍遞增棧就可以了,我們發現左邊進入的第一個比它大的是左
邊界,那麼出去的最後一個大於等於的一定是它的左邊界(考慮過程中彈出的 如果過
程中被彈出那麼我們只需要看棧中它前面哪個位置 如果與它相等那麼它的左邊界等於
它前面的否則他就是自己的左邊界。——類似路徑給出)
6.對棧內剩餘元素的處理 此時棧內元素左邊界均爲數組大小,同時可以判斷左邊界

PS:單調棧常用來處理全局下的最值、邊界等

C++代碼

#include<iostream>

using namespace std;
const int MAXN = 1e6 +500;
long long ele[MAXN],R[MAXN],L[MAXN],st[MAXN];
int n;

init (int num){
	for(int i = 0;i<num+5;i++){
		ele[i] = 0;R[i] = 0;L[i] = 0;st[i] = 0;
	}
}
void R2L(int r){
	if(r == 0) L[st[r]] = 0;
	if(r>=1 && ele[st[r]] > ele[st[r-1]] && L[st[r]] == 0) L[st[r]] = st[r];
	if(r>=1 && ele[st[r]] == ele[st[r-1]])L[st[r]] = st[r-1];
}
void solve(){
	int l = 1,r = 0;//模擬棧 r表示棧頂  棧內存ele元素下標 
	for(int i = 0;i<n;i++){
		if(r<l){st[r++] = i;continue;}
		while(r>=l&&ele[st[r]]>ele[i]){//改變棧頂以及右邊第一個比當前元素小的 
			R2L(r);
			L[i] = st[r];
			R[st[r--]] = i-1;
		}
		st[++r] = i;
	}
	while(r!=0){
		R2L(r);
		R[st[r--]] = n-1;
	}
}
int main(){
	long long ans = 0;
	while(1){
		cin>>n;
		if(n == 0) break;
		ans = 0;
		init(n);
		for(int i = 0;i<n;i++){
			cin>>ele[i];
		}
		solve();
		for(int i = 0;i<n;i++){
			L[i] = L[L[i]];//L類似路徑給出 
			if(ans<ele[i]*(R[i]-L[i]+1)) ans = ele[i]*(R[i]-L[i]+1);
		}
		cout<<ans<<endl;
	}
	return 0;
} 

TT’s Magic Cat

題目

題目題意:
有一組個數爲n的數組,有q組區間對應數組的區間,分別在qi區間加上數c,那麼最終這個數組會變成什麼樣子
Input&&Output:
Input&&Output
Sample:
Sample

題解

1.連續區間內的增量問題通常使用差分數組和前綴和共同解決
2.差分特點若B爲A的差分數組則在A的[m,n]區間內各加k那麼對於B來說隻影響了兩個
數
3.前綴和與差分,若B爲A的差分數組那麼B的前綴和就爲A
差分數組:B[0] = A[0] B[i] = A[i]-A[i-1](i>0)

C++代碼

#include<iostream>

using namespace std;
const int MAXN = 1e6+500;
long long A[MAXN],B[MAXN],Sumb[MAXN];
int n,q,c,l,r;

void solve(){
	for(int i = 0;i<n;i++){
		cin>>A[i];
		if(i == 0) B[i] = A[i];
		else B[i] = A[i] - A[i-1];
	}
	for(int i = 0;i<q;i++){
		cin>>l>>r>>c;
		B[l-1]+=c;
		if(r<n) B[r+1]-=c;
 	}
}
int main(){
	cin>>n>>q;
	solve();
 	for(int i = 0;i<n;i++){
 		if(i == 0) Sumb[i] = B[i];
		else Sumb[i] = Sumb[i-1]+B[i];
		cout<<Sumb[i]<<" ";
	}
	cout<<endl;
	return 0;
} 

C 平衡字符串

題目

題目
Input&&Output:
Input&&Output
Sample:

#Input
QWER
#Output
0

#Input
QQWE
#Output
1

#Input
QQQW
#Output
2

#Input
QQQQ
#Output
3

題解

1.分析題可知我們可以將本題轉化爲這樣一個問題,A,B,C爲三個區間字符數組,
其中B中內容未知A,C已知則可否通過填寫B使得A、B、C中的字符達到某種平衡
如果可以B最短爲多少
2.根據上述分析我們首先可以構造區間 [l,r]那麼原數組被分爲[0,l-1],[l,r],[r+1,n]
l,r可變,爲了保證遍歷的完整性 我們令 l=r=0,然後開始遍歷整個區間。
3.遍歷準則:
1)滿足條件的l,r,由於本題要求QWER四個字符數量相同,那麼對於區間[l,r]來說我們
首先需要將[0,l-1][r+1,n]中不平衡的字符補齊,根據最小原則也就是將所有字符數
量補充爲max(Q,W,E,R),如果剩餘元素爲4的倍數那麼滿足
2)如何完整遍歷:爲了完整遍歷,我們首先應該令l不變r自增直至滿足條件,此時
自增l縮小區間,若縮小後不滿足繼續自增r,直至遍歷完整個數組

總結:
對於這種判斷區間內連續的事情,並且LR有明確移動方向我們可以考慮這種方法——
尺取法

C++代碼

/*
尺取法:
常用於判斷連續區間內的事情
且L R移動有明確方向 
*/ 
#include<stdio.h>
#include<map>
using namespace std;
const int MAXN = 1e6+500;
char word[MAXN];
long long SUM[4]; 
int MAX = 0,l = 0,r = -1,total;
map<char,int> m;
int solve(int n){
	long long ans = n;
	while(1){
		MAX = max(max(SUM[0],SUM[1]),max(SUM[2],SUM[3]));
		total = r-l+1;
		long long mid = total - (4*MAX - SUM[0] - SUM[1] - SUM[2] - SUM[3]);
		//printf("%d %d %d %d %d %d %d\n",SUM[0],SUM[1],SUM[2],SUM[3],mid,r,r,l);
		if((mid < 0||mid % 4 != 0)&&r!=n-1){
			r++;
			SUM[m[word[r]]]--;
		}else{
			if(ans > total && mid>=0 && mid % 4 == 0) ans = total;
			//printf("%d %d %d\n",r,l,total);
			if(l>r || l == n-1) break;
			SUM[m[word[l]]]++;
			l++;
		}
	}
	return ans;
}
int main(){
	m['Q'] = 0;m['W'] = 1;m['E'] = 2;m['R'] = 3;
	int i = 0;
	char c;
	while(1){
		scanf("%c",&c);
		if(c == '\n') break;
		word[i] = c;
		SUM[m[c]]++;
		i++;
	}
	printf("%d\n",solve(i));
	return 0;
}

D 滑動窗口

題目

題目
Input&&Output:
Input&&Output
Sample:

#Input:
8 3
1 3 -1 -3 5 3 6 7

#Output:
-1 -3 -3 -3 3 3
3 3 5 5 6 7

題解

1.考慮一個窗口 [l,r] length = k(以求最大值爲例)
2.那麼這個窗口內的最大值我們可以視作 max(e[i屬於[l,r-1]],e[r]) 可以發現這
是一個局部最大值,而且根據甘岡類似於遞歸推max我們可以發現當 e[l]進入時假設
我們維持了窗口的大小,那麼他前面比他小的數都可以捨去瞭然後取出剩餘的他之前
比它大的數的最大值與他作比較,取更大的
3.具體實現,爲了維持窗口大小我們發現,當 r+1進入時 我們需要丟掉 l (如果有
元素),然後我們需要維持剩餘元素的大小關係,由於是最大值我們可以想到維持
一個遞增序列。
4.數據結構的選擇——由於我們需要從一端加另一端減 因此應該隊列是可以實現的
5.因此維持一個遞增隊列就好了(遞增隊列——隊列首最大(l) 隊列尾最小(r))
6.最小值構建一個遞減隊列就可以了

PS:對於這種局部性問題,並且跟滑動窗口類似的問題經常會用到單調隊列

C++代碼

#include<iostream>

using namespace std;
const long long MAXN = 1e6+500;
int que[MAXN],q[MAXN],que2[MAXN];//單調遞增隊列 隊列存下標 隊列首對應q元素最小 
long long n,k,l = 0,r = -1;//r隊列尾,l隊列首 
int MAX[MAXN],MIN[MAXN];
void solve_min(){
	l = 0,r = -1;
	for(int i = 0;i<n;i++){
		if(r<l) que[++r] = i;
		else{
			while(i - que[l]>=k&&r>=l) l++;//去除超框數 
			if(r<l) que[++r] = i;
			else{
				while(q[que[r]]>q[i] && r>=l) r--;
				que[++r] = i;
			}
		}
		MIN[i] = q[que[l]];
	} 
}

void solve_max(){
	l = 0;r = -1;
	for(int i = 0;i<n;i++){
		if(r<l) que[++r] = i;
		else{
			while(i - que[l]>=k&&r>=l) l++;
			if(r<l) que[++r] = i;
			else{
				while(q[que[r]]<q[i] && r>=l) r--;
				que[++r] = i;
			}
		}
		MAX[i] = q[que[l]];
	} 
}

int main(){
	std::ios::sync_with_stdio(false);
	cin>>n>>k;
	for(int i = 0;i<n;i++){
		cin>>q[i];
	}
	solve_max();
	solve_min();
	for(int i = k-1;i<n;i++){
		cout<<MIN[i]<<" ";
	}
	cout<<endl;
	for(int i = k-1;i<n;i++){
		cout<<MAX[i]<<" ";
	}
	cout<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章