给定一个长度为\(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; }
这个题目的重点在于最终状态的寻找,牢记贪心算法老是与数学规律挂钩(尤为是构造)