20240309
瑞士轮
思路:
快排会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;
}