知識總結
原理:
每一步都採取區域性最優解,取到最終的最優解。
常見時間複雜度
$ O(n)$ 或 $O(nlog (n))$
後者一般帶排序。
用法:
-
透過資料規模和題目資訊聯想貪心演算法常見時間複雜度
-
猜測結論
-
驗證合理性
- 歸納法
- 反證法(相鄰交換法):如果交換方案中相鄰的兩個元素/任意的兩個元素後,答案不會變得更好,那麼可以推定目前的解已經是最優解了。
後悔解法
無論當前的選項是否最優都接受,然後進行比較,如果選擇之後不是最優了,則反悔,捨棄掉這個選項;否則,正式接受。
(這種演算法支援對過往操作的“反悔”,但要求支援的反悔資訊和本身貪心的結構是相同的)
考場技巧
1.嘗試測試手模樣例,並和暴力對拍。
2.多寫幾個貪心拼湊出最優解。
3.有榜時觀察其他人的過題速度,若大家都過不去同一題,先跳過或選擇拿暴力等部分分。
隨堂練習
T1 小信吃甜筒
題目描述
小信想吃到 $n$ 個巧克力味的甜筒和 $m$ 個草莓味的甜筒,不能多吃。
冰櫃裡,有 $x$ 個巧克力味甜筒,飽食度分別為 $a_1, a_2,...,a_x$ ,有 $y$ 個草莓甜筒,飽食度分別為 $b_1, b_2,...,b_y$ ,有 $z$ 個原味甜筒,飽食度分別為 $c_1, c_2,...,c_z$ 。小信可以在原味甜筒上加調味料使其變成巧克力味或草莓味。
他想知道在不多吃的情況下能獲得的最大飽食值。
思路解析
由於不能多吃,所以我們可以先把原味甜筒加入,然後再取吃巧克力味甜筒和草莓味甜筒其中分別較大的。
然後直接排序選入後的數列,選取 $n+m$ 個最大的數,即可得到最大飽食值。
程式碼實現
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, m, l, x, y, z, a[N], b[N], c[N], v[N], flag, ans, ans1;
bool cmp1(int A, int B) {
return A > B;
}
signed main() {
scanf("%lld %lld %lld %lld %lld", &n, &m, &x, &y, &z);
for (int i = 1; i <= x; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= y; i++) {
scanf("%lld", &b[i]);
}
for (int i = 1; i <= z; i++) {
scanf("%lld", &c[i]);
v[i] = c[i];
}
sort(a + 1, a + 1 + x, cmp1);
sort(b + 1, b + 1 + y, cmp1);
for (int i = 1; i <= n; i++) {
v[i + z] = a[i];
}
for (int i = 1; i <= m; i++) {
v[i + z + n] = b[i];
}
sort(v + 1, v + 1 + n + m + z, cmp1);
for (int i = 1; i <= n + m; i++) {
ans += v[i];
}
printf("%lld", ans);
return 0;
}
T2 替換字母 2
雙倍經驗: https://www.luogu.com.cn/problem/P2882
題目描述
有長度為 $n$ 的字串,僅包含小寫字母。小信想把字串變成只包含一種字母。他每次可以選擇一種字元 $c$ ,然後把長度最多為 $m$ 的子串中的字元都替換成 $c$ 。
問小信最少需要多少次操作,才能把字串變成只包含一種字母?
思路解析
我們可以列舉所有可能的字元 $c$ ,然後計算替換操作次數。
對於每個字元 $c$ ,如果當前符合則跳過,否則替換長度為 $m$ 的子串中的字元。
程式碼實現
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m, ans = inf, cnt;
string a;
int found(char x) {
cnt = 0;
for (int i = 0; i < n;) {
if (a[i] == x) {
i++;
} else {
cnt++;
i += m;
}
}
return cnt;
}
int main() {
cin >> n >> m >> a;
for (char i = 'a'; i <= 'z'; i++) {
ans = min(ans, found(i));
}
printf("%d\n", ans);
return 0;
}
T3 異或與乘積
題目描述
有 $n$ 個數你可以將兩個數字異或,最終將所有數字相乘,問總乘積最大是多少
由於答案可能很大,輸出對 $1 000 000 007$ 取模後的結果。
思路解析
- 分離出 1 的位置:首先,我們可以找出所有數中為 1 的位,並將這些數分離出來。這些數在異或運算中起到關鍵作用。
- 處理偶數的情況:偶數 $\oplus$ $1$ = 偶數 $+$ $1$,奇數 $\oplus$ $1$ $=$ 奇數 $-$ $1$因此利用和一定,差小積大:在分組時,我們希望兩組的異或結果儘可能接近,這樣它們的差值就會小,根據乘法的性質,差值小的兩個數相乘會得到更大的乘積。
- 計算乘積:計算組中所有數的乘積。
- 最終乘積:將兩組的乘積相乘,並取模 $1000000007$ 。
程式碼實現
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, cnt1, a[N], ans = 1, mod = 1e9 + 7;
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
if (a[i] == 1) {
cnt1++;
}
}
for (int i = 1; i <= n; i++) {
if (!cnt1) {
break;
}
if (!(a[i] & 1)) {
cnt1--;
a[i]++;
}
}
for (int i = 1; i <= n; i++) {
ans = (ans % mod * a[i] % mod) % mod;
}
printf("%lld\n", ans);
return 0;
}
T4 國王遊戲
題目描述
恰逢 H 國國慶,國王邀請 $n$ 位大臣來玩一個有獎遊戲。首先,他讓每個大臣在左、右手上面分別寫下一個整數,國王自己也在左、右手上各寫一個整數。然後,讓這 $n$ 位大臣排成一排,國王站在隊伍的最前面。排好隊後,所有的大臣都會獲得國王獎賞的若干金幣,每位大臣獲得的金幣數分別是:排在該大臣前面的所有人的左手上的數的乘積除以他自己右手上的數,然後向下取整得到的結果。
國王不希望某一個大臣獲得特別多的獎賞,所以他想請你幫他重新安排一下隊伍的順序,使得獲得獎賞最多的大臣,所獲獎賞儘可能的少。注意,國王的位置始終在隊伍的最前面。
思路解析
本題資料範圍較大,因此我們需要使用高精度演算法來解決。
對於每一個大臣,他的貢獻值就是前面所有大臣的左手的貢獻值除以他的右手的貢獻值。
如果我們用反證法來驗證合理性,那麼我們可以找到如果任意交換兩個大臣都會使答案更劣,那麼我們就證明了答案是最優的。
那麼對於每兩個大臣,如果將這兩個單獨處理,則前面大臣的貢獻值設為 $x$
則如果將大臣 A 放在大臣 B 前面的貢獻值就是 $x·a.l·b.l/b.r$
則如果將大臣 B 放在大臣 A 前面的貢獻值就是 $x·b.l·a.l/a.r$
比較這兩個貢獻值,我們可以得到一個不等式:
若將大臣 A 放在大臣 B 前面貢獻值更優,則有 $a.l/b.r<b.l/a.r$ ,化簡為 $a.l·a.r<b.l·b.r$
程式碼實現
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e4 + 10;
struct node {
int l, r;
} q[N];
int n, x, y, cina, cinb;
vector<int> a, b;
bool cmp(node A, node B) {
return A.l * A.r < B.l * B.r;
}
namespace High {
void gjc(int x) {
vector<int> te;
int temp = 0;
for (int i = 0; i < a.size(); i++) {
temp += (a[i] * x);
te.push_back(temp % 10);
temp /= 10;
}
while (temp > 0) {
te.push_back(temp % 10);
temp /= 10;
}
a = te;
}
void gjch(int x) {
vector<int> te;
int r = 0;
for (int i = a.size() - 1; i >= 0; i--) {
r = r * 10 + a[i];
te.push_back(r / x);
r %= x;
}
reverse(te.begin(), te.end());
while (!te.empty() && te.back() == 0) {
te.pop_back();
}
if (te.size() > b.size())
b = te;
if (!te.empty() && te.size() == b.size()) {
for (int i = te.size() - 1; i >= 0; i--) {
if (te[i] > b[i]) {
b = te;
break;
} else if (te[i] == b[i])
continue;
else if (te[i] < b[i])
break;
}
}
}
} // namespace High
using namespace High;
signed main() {
scanf("%lld", &n);
scanf("%lld %lld", &x, &y);
for (int i = 1; i <= n; i++) {
scanf("%lld %lld", &cina, &cinb);
q[i].l = cina;
q[i].r = cinb;
}
sort(q + 1, q + 1 + n, cmp);
while (x) {
a.push_back(x % 10);
x /= 10;
}
for (int i = 1; i <= n; i++) {
gjch(q[i].r);
gjc(q[i].l);
}
if (!b.empty()) {
for (int i = b.size() - 1; i >= 0; i--) {
printf("%lld", b[i]);
}
} else {
printf("0");
}
return 0;
}
課後作業
待更
//TODO