給定一個長度爲\(n\)的序列\(a\),每次你能夠從中選擇相鄰但不相等的兩個元素\(a_i\),\(a_{i+1}\),將這兩個數進行合而且替換爲\(a_i + a_{i+1}\)。例如 \([7,4,3,7] \rightarrow [7,4+3,7]\),直到沒法繼續進行這樣的操做,最後返回最終序列的長度。c++
1 <= n <= 2*10^5
git
1 <= ai <= 10^9
算法
考點: greedy
math
*800
數組
經過思考咱們能夠發現,除非序列全爲相同的數字,不然咱們必定能夠將最後的序列長度壓縮至\(1\)。ide
所以能夠寫出以下代碼:spa
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 50; int f[maxn]; void solve(){ int n; cin >> n; for (int i = 0; i < n; ++ i) cin >> f[i]; sort(f, f + n); if (f[0] == f[n - 1]) cout << n << '\n'; else cout << "1\n"; } int main(){ int t; cin >> t; while (t--) solve(); return 0; }
給定一個長度爲\(n\)的序列\(a\),對其進行\(k\)次操做,每次操做爲:code
- 取出當前序列的最大值\(d\)、
- 對於序列中的每一個數\(a_i\),將其變爲\(d - a_i\)。
返回進行\(k\)次操做以後序列的狀況。ip
1 <= n <= 2*10^5
ci
1 <= k <=10^18
get
考點: implementation
math
*800
在進行第一次操做後,序列中的最大值和最小值已經固定,其中最大值爲:\(\max{arr} - \min{arr}\),最小值爲\(0\),以後再進行操做其實是一種週期性的重複。
(小tips:在看到\(k\)的數據範圍時,能夠猜想這題是一個週期性問題!)
#include <bits/stdc++.h> using namespace std; using LL = long long; const int maxn = 2e5 + 50; int f[maxn]; int ans[maxn][2]; int main(){ int t; cin >> t; while (t--){ int n; LL k; cin >> n >> k; int mn = 0x3f3f3f3f, mx = 0xc0c0c0c0; for (int i = 0; i < n; ++ i){ cin >> f[i]; mn = min(mn, f[i]); mx = max(mx, f[i]); } for (int i = 0; i < n; ++ i) ans[i][0] = mx - f[i]; for (int i = 0; i < n; ++ i) ans[i][1] = (mx - mn) - ans[i][0]; if (k & 1){ for (int i = 0; i < n; ++ i) cout << ans[i][0] << (i == n - 1 ? '\n' : ' '); }else { for (int i = 0; i < n; ++ i) cout << ans[i][1] << (i == n - 1 ? '\n' : ' '); } } return 0; }
給定一個長度爲 \(n\) 的數組 \(a\) ,每次能夠選取一個非降序列,使得序列中每一個值增長一。問最少操做多少次使得整個數組 非降
(\(1 \leq n \leq 2 * 10^5\))
(\(0 \leq a_i \leq 10^9\))
greedy
implementation
*1200
分析後能夠發現,只須要考慮谷底值。因爲要求整個序列非降,所以咱們只須要考慮\(a_i\)與\(a_{i - 1}\)的關係。(左值)
通俗的話來說,每次你只須要考慮可否知足當前值\(a_i\)大於等於前一個值\(a_{i - 1}\)。
#include <bits/stdc++.h> using namespace std; using LL = long long; const int maxn = 2e5 + 50; int f[maxn]; void solve(){ int n; cin >> n; for (int i = 0; i < n; ++ i) cin >> f[i]; LL ans = 0; for (int i = 0; i + 1 < n; ++ i){ ans += max(f[i] - f[i + 1], 0); // 只須要考慮左值 } cout << ans << '\n'; } int main(){ int t; cin >> t; while (t--) solve(); return 0; }
從 \(1\) 到 \(n\)圍成一個圈, 每一個人能夠選擇攻擊左邊(
L
)的人,或者右邊(R
)的人。且須要知足如何規則:
- 當只被一我的攻擊時,必須攻擊這我的。
- 當沒有被攻擊或者被兩我的攻擊時,能夠攻擊身邊的任意一我的。
給定一個長度爲 \(n\) 的序列表明攻擊狀況,問最少須要修改幾回序列能使其知足規則。
(\(3 \leq n \leq 2*10^5\))
考點:constructive algorithms
dp
greedy
string suffix structures
*1700
通過分析,能夠發現只要存在LLL
,RRR
都是不合理的。要進行替換。也就是將其中某個位置的字符改變。
以後,須要考慮如何對於這種LLL...LLL
序列進行處理能使獲得的結果最優。
RRLRR
改變一個字符,最多能處理長度爲 5 的重複串RRLRRLRR
改變兩個字符,最多處理長度爲 8 的重複串而後咱們能夠得出結論:對於長度爲 \(n,n \ge 3\) 的重複串(注意,這裏咱們沒有考慮首尾鏈接的狀況),咱們只須要改變 \(\frac{n}{3}\) 次
如今考慮首位鏈接的狀況,當首尾鏈接時,長度爲 \(n,n \ge 3\) 的重複串須要進行幾回操做,因爲首尾相連,所以首端和尾端能放置的字符從原來的 2 個,到如今的最多 1 個。也就是從RRLRR
改變爲 RLR
。所以咱們只須要改變 \(\frac{n + 2}{3}\) 次。(逆向思惟,認爲咱們重複串的長度爲 \(n + 2\))。
在書寫代碼的時候,能夠首先將尾部與首部相同的字符去除,並修改計數器(至關於將其轉移到首部)。以後判斷是否整個序列爲相同字符,若整個序列相同則特殊處理。
#include <bits/stdc++.h> using namespace std; void solve(){ int n; cin >> n; string ss; cin >> ss; int cnt = 0, ans = 0; // 將尾部轉移到首部(經過修改 cnt 完成) while (!ss.empty() && ss[0] == ss.back()){ ++ cnt; ss.pop_back(); } if (ss.empty()){ if (cnt <= 2){ cout << 0 << '\n'; return; } if (cnt == 3){ cout << 1 << '\n'; return; } cout << (cnt + 2) / 3 << '\n'; return; } ss += '$'; // 添加一個字符,保證能所有處理完成 for (int i = 0; i + 1< ss.size(); ++ i){ ++ cnt; if (ss[i] != ss[i + 1]){ ans += (cnt / 3); cnt = 0; } } cout << ans << '\n'; } int main(){ int t; cin >> t; while (t--) solve(); return 0; }
該題還有 dp
解法,後面補上
給定一個 \(n\) ,生成一個\(n \times n\)的矩陣。要求給定一個\(k\)值,輸出惟一肯定的路徑 \((1, 1) \rightarrow (n, n)\) 如此。
bitmasks
constructive algorithms
math
*2100
新知識點補充(這題還不夠熟練):
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(){ int n; cin >> n; vector<vector<LL> > mat(n + 1, vector<LL>(n + 1 , 0)); for (int i = 1; i <= n; ++ i){ for (int j = 1; j <= n; ++ j){ if (i & 1) cout << "0 "; else cout << (1LL << (i + j)) << ' '; } cout << endl; } cout.flush(); int q; cin >> q; while (q--){ LL sum; cin >> sum; cout << "1 1\n"; int row = 1, col = 1; for (int k = 1; k <= 2 * n - 2; ++ k){ int cur = col + row; if (row & 1){ if (sum & (1LL << (cur + 1))) ++ row; else ++ col; }else{ if (sum & (1LL << (cur + 1))) ++ col; else ++ row; } cout << row << " " << col << endl; } } cout.flush(); return 0; }
給定一個長度爲 \(n\) 的升序序列 \(H\) ,任意時刻序列中存在 \(h_i + 2 \leq h_{i + 1}\) 時,發生「滑坡」,即\(h_i\) 加一, \(h_{i +1}\) 減一。且全部滑坡同時進行。請問最後序列\(H\)的最終狀態。
\(1 \leq n \leq 10^6\)
\(0 \leq hi \leq h_{i+1} \leq 10^{12} \quad \forall \;i \in [1, n]\)
constructive algorithms
data structures
greedy
math
*2400
(這部分爲特殊思路:看到 \(n\) 的規模以及最終時刻這兩個字眼,我內心就想到了這題是一個數學
,貪心
,構造
題。後面補題的時候發現果真是的)
F題的解題核心在於獲得最終狀態的條件。考慮到實際上只須要判斷四個點就能夠模擬滑坡的過程,咱們選取\(a_{i - 1}, a_{i}, a_{i + 1}, a_{i + 2}\)進行考慮。咱們假設\(a_{i},a{i - 1}\)之間;\(a_{i + 1}, a_{i + 2}\)之間不會進行滑坡,意味着\(a_{i + 2} - a_{i + 1} \leq 1\)(0, 1兩種狀態)。設當前\(a_{i + 1} - a{i} == 2\),進行滑坡以後就會出現\(a_{i} == a_{i + 1}\)。而因爲\(a_{i + 1}\)減小了一,因此\(a_{i + 2} - a_{i + 1} \ge 1\)因此,每一個新的等式出現必定會破壞原有的一個等式
整個序列中最終只會出現最多一對相等的元素
咱們能夠經過貪心構造出一個遞增的數列,將剩下未分配的值貪心分配給前幾個元素。
#include <bits/stdc++.h> using namespace std; using LL = long long; inline LL read(){ /* 注意假如爲 long long 時須要修改 */ char c = getchar(); LL x = 0, f = 1; while (!isdigit(c)) { if (c == '-') f = -1; c = getchar(); } while (isdigit(c)) x = (x << 3) + (x << 1) + (c^48), c = getchar(); return f * x; } const int maxn = 1e6 + 50; LL a[maxn]; int main(){ LL n; n = read(); LL sum = 0; for (int i = 0; i < n; ++ i) a[i] = read(), sum += a[i]; sum -= (n * (n + 1)) / 2; LL eve, lvf; eve = sum / n; lvf = sum % n; for (int i = 0; i < n; ++ i){ cout << (i + 1) + eve + (i + 1 <= lvf) << (i == n ? '\n' : ' '); } return 0; }
這個題目的重點在於最終狀態的尋找,牢記貪心算法老是與數學規律掛鉤(尤爲是構造)