「程序設計思維與實踐」Week5 作業:A - 最大矩形、B - TT's Magic Cat、D - 滑動窗口。單調棧+前綴和+尺取法+單調隊列

A - 最大矩形 HDU - 1506

題目描述

給一個直方圖,求直方圖中的最大矩形的面積。例如,下面這個圖片中直方圖的高度從左到右分別是2, 1, 4, 5, 1, 3, 3, 他們的寬都是1,其中最大的矩形是陰影部分。在這裏插入圖片描述

input

輸入包含多組數據。每組數據用一個整數n來表示直方圖中小矩形的個數,你可以假定1 <= n <= 100000. 然後接下來n個整數h1, …, hn, 滿足 0 <= hi <= 1000000000. 這些數字表示直方圖中從左到右每個小矩形的高度,每個小矩形的寬度爲1。 測試數據以0結尾。

Output

對於每組測試數據輸出一行一個整數表示答案。

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000

題解

  • 思路:對於每一個柱形的高找到最優的面積,即左右都達到最寬。樸素方法爲從此柱形開始左右都尋找一遍複雜度是n^2,而單調棧,可以在遍歷的過程中,通過維護一個單調關係,當單調不合法時,記錄彈出元素的左右端點,將複雜度降爲O(n).
  • 具體實現爲:左右都跑一邊,記錄每個柱形的最左和最右,最後高度乘以r-l+1取所有max,就是最後答案。

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;

typedef long long LL;
const int MAXN = 100000+10;
int h[MAXN], l[MAXN], r[MAXN];

int main() {
	int n;
	while (cin >> n) {
		if (!n) break;
		for (int i = 0; i < n; ++i) {
			scanf("%d", &h[i]);
		}
		stack<int> s;
		s.push(0);
		for (int i = 1; i < n; ++i) {
			while (s.size() && h[i] < h[s.top()]) {
				r[s.top()] = i;
				s.pop();
			}
			s.push(i);
		}
		while (s.size()) {
			r[s.top()] = n;
			s.pop();
		}

		s.push(n-1);
		for (int i = n-2; i >= 0; --i) {
			while (s.size() && h[i] < h[s.top()]) {
				l[s.top()] = i;
				s.pop();
			}
			s.push(i);
		}
		while (s.size()) {
			l[s.top()] = -1;
			s.pop();
		}
		LL ans = 0;
		for (int i = 0; i < n; ++i) {
			// cout << l[i] << " " << r[i] << endl;
			ans = max(ans, (LL)((LL)h[i]*(LL)(r[i]-l[i]-1)));
		}
		cout << ans << endl;
	}
	return 0;
}

B - TT’s Magic Cat Gym - 272542A

題目描述

Thanks to everyone’s help last week, TT finally got a cute cat. But what TT didn’t expect is that this is a magic cat.
One day, the magic cat decided to investigate TT’s ability by giving a problem to him. That is select n cities from the world map, and a[i] represents the asset value owned by the i-th city.
Then the magic cat will perform several operations. Each turn is to choose the city in the interval [l,r] and increase their asset value by c. And finally, it is required to give the asset value of each city after q operations.
Could you help TT find the answer?

input

在這裏插入圖片描述

Output

Print n integers a1,a2,…,an one per line, and ai should be equal to the final asset value of the i-th city.

Sample Input 1

4 2
-3 6 8 4
4 4 -2
3 3 1

Sample Output 1

-3 6 9 2

Sample Input 2

2 1
5 -2
1 2 4

Sample Output 2

9 2

Sample Input 3

1 2
0
1 1 -8
1 1 -6

Sample Output 3

-14

題解

  • 思路:每次操作如果暴力遍歷複雜度是n^2不可行,考慮到每次操作都是對連續區間的加減操作我們可以考慮:能否快速的把一次修改區間的操作表示出來?因爲每次操作都是獨立的,而且加減操作沒有順序性,我們完全可以分別記錄然後最後累加所有的狀態。另一方面,對於一個區間內的狀態,我們記錄一個標記,這個標記當遍歷到區間左端點開啓,右端點關閉,這樣就O(1)記錄了整個區間的變化。
  • 實現:先根據輸入給區間加標記,即左閉爲變化值,右開抵消變化值,最後取標記的前綴和即可。

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;

typedef long long LL;
const int MAXN = 200000+10;
LL a[MAXN], c[MAXN];

int main() {
	int n, q;
	cin >> n >> q;
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", &a[i]);
	}
	while (q--) {
		int l, r;
		LL ci;
		scanf("%d%d%lld", &l, &r, &ci);
		c[l] += ci;
		c[r+1] -= ci;
	}
	c[0] = 0;
	for (int i = 1; i <= n; ++i) {
		c[i] += c[i-1];
		printf("%lld ", a[i]+c[i]);
	}
	return 0;
}

C - 平衡字符串 Gym - 270737B

題目描述

一個長度爲 n 的字符串 s,其中僅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四種字符。
如果四種字符在字符串中出現次數均爲 n/4,則其爲一個平衡字符串。
現可以將 s 中連續的一段子串替換成相同長度的只包含那四個字符的任意字符串,使其變爲一個平衡字符串,問替換子串的最小長度?
如果 s 已經平衡則輸出0。

input

一行字符表示給定的字符串s

Output

一個整數表示答案

Example

Input
QWER
Output
0
Input
QQWE
Output
1
Input
QQQW
Output
2
Input
QQQQ
Output
3

題解

  • 對於已知的 qwer 的個數,想要變成合法的序列,那麼我們需要把小的部分像大的部分標齊,而我們判斷一個區間是否能用來把qwer標齊只需要判斷其個數是否能夠達到用來填補的個數。這樣在區間遍歷的過程中很簡單的就可以維護qwer的個數,又能得知當前區間的長度。所以可以用尺取法。
  • 實現:qwer分別記錄「除了當前選中的區間」之外qwer的個數。採用尺取法的兩個步驟:1.右端點往右找到第一個合法的右端點,2.左端點不斷朝右縮小,直到滿足合法情況長度最小,記錄答案即可。判斷合法的過程:計算當前區間長度與區間外需要的填補的長度,如果大於且能夠是4的倍數則合法。4的倍數的原因:填補之後如果有剩餘,剛好再增加一組qwer。

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;

const int MAXN = 100000+10;
char s[MAXN];
int q, w, e, r;

bool check(int L, int R) {
	// cout << "--------- " << l << "," << r << endl;
	// cout << "qwer" << q << " " << w << " " << e << " " << r << endl;
	int theMax = max(q,w);
	theMax = max(e,theMax);
	theMax = max(r,theMax);
	int need = 4*theMax-q-w-e-r;
	int now = R-L+1;
	int free = now-need;
	// cout << now << ", " << need << endl;
	if (free >= 0 && free%4 == 0) return true;
	return false;
}

int main() {
	scanf("%s", s);
	int len = strlen(s);
	q = w = e = r = 0;
	for (int i = 0; i < len; i++) {
		if (s[i] == 'Q') q++; 
		else if (s[i] == 'W') w++; 
		else if (s[i] == 'E') e++; 
		else if (s[i] == 'R') r++; 
	}
	int ans = len;

	if (check(0,-1)) {
		ans = 0;
		cout << ans << endl;
		return 0;
	}

	if (s[0] == 'Q') q--;
	else if (s[0] == 'W') w--; 
	else if (s[0] == 'E') e--; 
	else if (s[0] == 'R') r--; 

	int L = 0, R = 0;
	while (R < len) {
		while(L <= R && check(L,R)) {
			ans = min(ans, R-L+1);
			L++;
			if (s[L-1] == 'Q') q++; 
			else if (s[L-1] == 'W') w++; 
			else if (s[L-1] == 'E') e++; 
			else if (s[L-1] == 'R') r++; 
			// cout << "lll " << L << "," << R << endl;
		}
		while (R < len && !check(L,R)) {
			R ++;
			if (s[R] == 'Q') q--;
			else if (s[R] == 'W') w--; 
			else if (s[R] == 'E') e--; 
			else if (s[R] == 'R') r--; 
			// cout << "rrr " << L << "," << R << endl;
		}
		if(R >= len) break;
	}
	cout << ans << endl;

	return 0;
}


D - 滑動窗口 POJ - 2823

題目描述

ZJM 有一個長度爲 n 的數列和一個大小爲 k 的窗口, 窗口可以在數列上來回移動. 現在 ZJM 想知道在窗口從左往右滑的時候,每次窗口內數的最大值和最小值分別是多少. 例如:
數列是 [1 3 -1 -3 5 3 6 7], 其中 k 等於 3.
在這裏插入圖片描述

Input

輸入有兩行。第一行兩個整數n和k分別表示數列的長度和滑動窗口的大小,1<=k<=n<=1000000。第二行有n個整數表示ZJM的數列。

Output

輸出有兩行。第一行輸出滑動窗口在從左到右的每個位置時,滑動窗口中的最小值。第二行是最大值。

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Input

-1 -3 -3 -3 3 3
3 3 5 5 6 7

題解

  • 由於最小值和最大值是同一性質的問題,問題可以變爲:如果在滑動窗口的過程中維護最值。本題需求:將下一個要移動到的值與現有的值進行比較;將已經滑出的值刪除。於是考慮單調棧,既可以維護前端的最值,又可以對後端已經不在窗口範圍內的值刪除,從而獲取了當前區間的最值。
  • 實現:用的stl deque,感覺手動模擬對於數組特殊數據可能會有很大的浪費,複雜的實現又沒必要了。單調隊列:隊列中存儲的是元素位置,每次往右移動1,然後計算出此時左端點應該在哪,隊列中值在左端點之前的直接刪掉;然後處理右側,將下一個值與隊列中已有的進行比較,如果我們要的是最小值,那麼就把大於的都刪掉,如果要的是最大值,那麼把小於的都彈出,最後把這個值推進去。每次移動後隊尾的元素就是維護的最值。

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;

const int MAXN = 1000000+10;
int n, k;
int a[MAXN];


int main() {
	cin >> n >> k;
	for(int i = 0; i < n; ++i)
		scanf("%d", &a[i]);
	deque<int> q;
	int r = 0;
	for(r = 0; r < k; ++r) {
		while (q.size() && a[r] <= a[q.front()]) {
			q.pop_front();
		}
		q.push_front(r);
	}
	r--;
	cout << a[q.back()] << " ";
	while (r+1 < n) {
		r++;
		int l = r-k+1;
		if (l > q.back()) {
			q.pop_back();
		}
		while (q.size() && a[r] <= a[q.front()]) {
			q.pop_front();
		}
		q.push_front(r);
		cout << a[q.back()] << " ";
	}
	cout << endl;

	while (q.size()) q.pop_front();
	for(r = 0; r < k; ++r) {
		while (q.size() && a[r] >= a[q.front()]) {
			q.pop_front();
		}
		q.push_front(r);
	}
	r--;
	cout << a[q.back()] << " ";
	while (r+1 < n) {
		r++;
		int l = r-k+1;
		if (l > q.back()) {
			q.pop_back();
		}
		while (q.size() && a[r] >= a[q.front()]) {
			q.pop_front();
		}
		q.push_front(r);
		cout << a[q.back()] << " ";
	}
	cout << endl;	


	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章