瑞士輪
思路:
快排會g,所以要歸併排序
define int long long會g,關掉
快排函式: stable_sort,用法和sort一樣
#include <bits/stdc++.h>
using namespace std;
// #define int long long
struct inf {
int score;
int id;
int force;
};
bool cmp(inf a, inf b) {
if(a.score == b.score) {
return a.id < b.id;
}
return a.score > b.score;
}
void solve() {
int N, R, Q;
cin >> N >> R >> Q;
N *= 2;
vector<inf> v(N);
for (int i = 0; i < N; i++) {
v[i].id = i + 1;
cin >> v[i].score;
}
for (int i = 0; i < N; i++) {
cin >> v[i].force;
}
stable_sort(v.begin(), v.end(), cmp);
for (int i = 0; i < R; i++) {
for (int j = 0; j < N; j += 2) {
if(v[j].force > v[j + 1].force) {
v[j].score++;
}
else {
v[j + 1].score++;
}
}
stable_sort(v.begin(), v.end(), cmp);
}
cout << v[Q - 1].id << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) {
solve();
}
return 0;
}
其實這是靠歸排卡過去的,正解如下:
在第一次排完序之後的第一場比賽結束後,贏者的順序和敗者的順序相對不變,就是兩個集合,因為贏都是加一分,所以我們考慮把兩個順序不變的陣列合並起來:歸併!
#include <bits/stdc++.h>
using namespace std;
// #define int long long
struct inf {
int score;
int id;
int force;
};
bool cmp(inf a, inf b) {
if(a.score == b.score) {
return a.id < b.id;
}
return a.score > b.score;
}
void solve() {
int N, R, Q;
cin >> N >> R >> Q;
N *= 2;
vector<inf> v(N);
for (int i = 0; i < N; i++) {
v[i].id = i + 1;
cin >> v[i].score;
}
for (int i = 0; i < N; i++) {
cin >> v[i].force;
}
stable_sort(v.begin(), v.end(), cmp);
for (int i = 0; i < R; i++) {
for (int j = 0; j < N; j += 2) {
if(v[j].force > v[j + 1].force) {
v[j].score++;
}
else {
v[j + 1].score++;
}
}
stable_sort(v.begin(), v.end(), cmp);
}
cout << v[Q - 1].id << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) {
solve();
}
return 0;
}
B - 傳送門
思路:
首先開兩個邊值陣列
四重迴圈,列舉邊權為0的邊需要兩重迴圈[i, j]
然後再列舉兩個點[k, g]看是否更新邊權,看[k, i] + [j, g] 和[k, j] + [i, g],這兩條邊是否能更新[k,g]
#include <bits/stdc++.h>
using namespace std;
#define int long long
int e[105][105];
int ee[105][105];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if(i != j) {
e[i][j] = 1e18;
}
else {
e[i][j] = 0;
}
}
}
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
e[u][v] = e[v][u] = w;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if(e[i][k] != 1e18 && e[k][j] != 1e18) {
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
}
}
}
int ans = 1e18;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
for (int g = 1; g <= n; g++) {
ee[k][g] = e[k][g];
}
}
ee[i][j] = ee[j][i] = 0;
for (int k = 1; k <= n; k++) {
for (int g = 1; g <= n; g++) {
ee[k][g] = min(ee[k][g], min(ee[k][i] + ee[j][g], ee[k][j] + ee[i][g]));
}
}
int res = 0;
for (int k = 1; k <= n; k++) {
for (int g = k + 1; g <= n; g++) {
res += ee[k][g];
}
}
ans = min(ans, res);
}
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) {
solve();
}
return 0;
}
D - 菜餚製作
思路:
因為題目有邊的限制條件(正常拓撲)然後要輸出序號的順序儘可能小的先輸出
為什麼要反向拓撲?
我理解的是,正向拓撲優先佇列小的先輸出不行,因為題目要求優先輸出小的,但是小的可能藏在大的後面,所以正向不能貪心
但是反向貪心是正確的:因為我們保證反向先輸出大的,而反向的起點正是正向的終點,也就是輸出的結果,所以反向拓撲可以貪心保證正確性
#include <bits/stdc++.h>
using namespace std;
// #define int long long
vector<int> adj[100005];
void solve() {
int n, m;
cin >> n >> m;
vector<int> in(n + 1, 0);
for (int i = 1; i <= n; i++) {
adj[i].clear();
}
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
adj[v].push_back(u);
in[u]++;
}
priority_queue<int> q;
for (int i = 1; i <= n; i++) {
if(in[i] == 0) {
q.push(i);
}
}
vector <int> ans;
while(q.size()) {
int x = q.top();
ans.push_back(x);
q.pop();
for (auto it : adj[x]) {
if(--in[it] == 0) {
q.push(it);
}
}
}
if(ans.size() == n) {
for (int i = n - 1; i >= 0; i--) {
cout << ans[i] << " ";
}
cout << endl;
}
else {
cout << "Impossible!\n";
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
E - 資料備份
題意:
n個點,n個點距離起點的距離已知,要求連k條邊,2k個邊的頂點相異,求這k跳變的最小長度和
思路:
題目範圍是 $N[1, 10 ^ 5]$ 時間是500ms,要求演算法是O(nlogn)級別的
這個題是個反悔貪心,核心機制是選一個點,然後這個點的權值設定成 (l + r) - x,如果再選這個就相當於選了兩邊的然後扔掉中間的,妙
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct node {
int l, r;
int id;
int val;
};
struct inf {
int val;
int id;
bool operator< (const inf & a) const {
return val > a.val;
}
};
void solve() {
int n, m;
cin >> n >> m;
vector<int> vv(n);
for (int i = 0; i < n; i++) {
cin >> vv[i];
}
vector<int> v;
v.push_back(0);
for (int i = 1; i < n; i++) {
v.push_back(vv[i] - vv[i - 1]);
}
priority_queue<inf> q;
for (int i = 1; i < n; i++) {
q.push({v[i], i});
}
// 1 - 4
vector<int> vis(n + 1, 0);
vector<node> lst(n + 1);
lst[0].val = lst[n].val = (int)(9e18);
for (int i = 1; i < n ; i++) {
lst[i].l = i - 1;
lst[i].r = i + 1;
lst[i].id = i;
lst[i].val = v[i];
}
int ans = 0;
int cnt = 0;
while(cnt < m) {
auto it = q.top();
q.pop();
// cerr << it.id << " " << it.val << endl;
if(vis[it.id] == 1) {
continue;
}
ans += it.val;
int _l = lst[it.id].l;
int _r = lst[it.id].r;
vis[_l] = 1, vis[_r] = 1;
lst[it.id].val = lst[_l].val + lst[_r].val - lst[it.id].val;
q.push({lst[it.id].val, it.id});
lst[it.id].l = lst[_l].l;
lst[it.id].r = lst[_r].r;
lst[lst[_l].l].r = it.id;
lst[lst[_r].r].l = it.id;
cnt++;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
反悔貪心
-
前兩道題都是利用堆的特性,維護一個利益最大值,每次更新失敗的時候:把元素壓入堆,把堆頂元素去除。
-
反悔貪心允許我們在發現現在不是全域性最優解的情況下,回退一步或者若干步,去採取另外的策略得到全域性最優解。
P2949 [USACO09OPEN] Work Scheduling G
題意:
n件事,每件事有截止日期和收益,有1e9的時間(無限長),但是每一秒只能做一件事,一件事只需要一秒。
問能獲得的最大收益?
思路:
起初就是按照時間第一關鍵字,收益第二關鍵字排序,然後只要時間夠就加,貪心只有40分
我們思考一下,如果在後期有收益比前期高的事件,可以放棄做前面那個,優先做後面那個
實現:按第一關鍵字排序之後,小根堆維護事件收益。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
vector<pair<int, int>> v(n);
for (int i = 0; i < n; i++) {
cin >> v[i].first >> v[i].second;
}
sort(v.begin(), v.end());
priority_queue<int, vector<int>, greater<int>>q;
int day = 0;
int res = 0;
for (auto [t, w] : v) {
if(day + 1 <= t) {
res += w;
q.push(w);
day++;
}
else {
if(q.top() < w) {
res -= q.top();
q.pop();
q.push(w);
res += w;
}
}
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
P4053 [JSOI2007] 建築搶修
題意:
n個建築需要修復,對於每個建築,給兩個引數$t1, t2$,表示修築需要的時間和修築的截止時間。
問能最多能修復多少建築。
思路:
這個題的特點就是選擇一種策略,來找最多的東西,看著像揹包dp,其實是個反悔貪心。
先搞清楚怎麼才能讓建築儘可能多?那就是建築所需要的時間儘可能少。
利用大根堆維護修復建築所需要的時間。
我們先根據t2排序,然後迴圈遍歷
- 如果當前的時間足以修復這個建築,那就修
- 否則,就說明之前的時間已經過了修復這個建築的時間,我們要把這個建築壓入大根堆,然後把最大的建築時間剔除,這就是反悔
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct inf{
int t1, t2;
};
bool cmp (inf a, inf b) {
return a.t2 < b.t2;
}
void solve() {
int n;
cin >> n;
vector<inf> v(n);
for (int i = 0; i < n; i++) {
cin >> v[i].t1 >> v[i].t2;
}
sort(v.begin(), v.end(), cmp);
priority_queue<int> q;
int sum = 0;
int ans = 0;
for (auto [t1, t2] : v) {
q.push(t1);
sum += t1;
if(sum <= t2) {
ans++;
}
else {
sum -= q.top();
q.pop();
}
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) {
solve();
}
return 0;
}
P2107 小Z的AK計劃
題意:
n個點,m的時間。
每個點有位置和停留時間,問最多解決的點數。
思路:
這個題我學會了,反悔貪心就是先做,後反悔,
就像這個題,我們直接按照位置順序排序,
然後對時間用大根堆維護,每次取優。
直到time < 0 我們開始出隊,如果出到time < 0,就break
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct inf {
int x, t;
};
bool cmp (inf a, inf b) {
return a.x < b.x;
}
void solve() {
int n, m;
cin >> n >> m;
vector<inf> v(n);
for (int i = 0; i < n; i++) {
cin >> v[i].x >> v[i].t;
}
int time = 10;
int loc = 0;
int ans = 0;
int res = 0;
sort(v.begin(), v.end(), cmp);
priority_queue<int> q;
for (auto [x, t] : v) {
if(time - x + loc - t >= 0) {
time -= t + x - loc;
q.push(t);
ans++;
loc = x;
}
else {
while(q.size()) {
time += q.top();
q.pop();
ans--;
if(time - x + loc - t >= 0) {
break;
}
}
if(time - x + loc - t >= 0) {
q.push(t);
time -= x + t;
}
else
}
res = max(res, ans);
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
Buy Low Sell High
題意:
n天,
上午進$a_i$件,下午有一個人買$b_i$ 件,問最多能滿足多少人的需求
思路:
貪心直接做會出現大客戶的情況,所以要反悔,能賣就賣,開一個大根堆,維護大客戶
最後輸出
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct inf {
int a, b;
};
struct inf2 {
int t;
int id;
bool operator<(const inf2& a) const{
return t < a.t;
}
};
void solve() {
int n;
cin >> n;
vector<inf> v(n);
for (int i = 0; i < n; i++) {
cin >> v[i].a;
}
for (int i = 0; i < n; i++) {
cin >> v[i].b;
}
int cnt = 0;
int ans = 0;
priority_queue<inf2> q;
for (int i = 0; i < n; i++) {
int a = v[i].a, b = v[i].b;
cnt += a;
q.push({b, i + 1});
cnt -= b;
if(cnt >= 0) {
ans++;
}
else {
cnt += q.top().t;
q.pop();
}
}
cout << ans << endl;
vector<int> vt;
while(q.size()) {
vt.push_back(q.top().id);
q.pop();
}
sort(vt.begin(), vt.end());
for (auto it : vt) {
cout << it << " ";
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}