「刷题记录」LOJ/一本通提高篇 贪心算法
[活动安排]
题目传送门:活动安排
思路:我们按照活动的结束时间进行升序排序,因为一个活动结束的越早,就越不容易与其他活动起冲突,从前往后枚举时判断一下就行了
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5;
int n, ans;
struct hd {
int str, las;
int operator < (const hd &b) const {
return las == b.las ? str < b.str : las < b.las;
}
} a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
int last = 0;
n = read();
for (int i = 1; i <= n; ++i) {
a[i].str = read(), a[i].las = read();
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) {
if(last <= a[i].str) {
ans++;
last = a[i].las;
}
}
printf("%d\n", ans);
return 0;
}
[种树]
题目传送门:种树
思路:
1、贪心
依然是按照路段的结束位置进行升序排序,以便于我们从前往后按顺序枚举,枚举每一个路段时,从路段的开头枚举到结尾,由于一个位置只能种一棵树,所以只要这个位置有树,这个路段要求的树的数量就-1,具体看代码吧
贪心 代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 3e4 + 5;
int n, h, ans;
int vis[N];
struct dl {
int str, las, w;
int operator < (const dl &b) const {
return las < b.las;
}
} d[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read(), h = read();
for (int i = 1; i <= h; ++i) {
d[i].str = read();
d[i].las = read();
d[i].w = read();
}
sort(d + 1, d + h + 1);
for (int i = 1; i <= h; ++i) {
int l = d[i].str, r = d[i].las, sum = d[i].w;
for (int j = l; j <= r && sum; ++j) {
if (vis[j]) {
--sum;
}
}
for (int j = r; j >= l && sum; --j) {
if (!vis[j]) {
vis[j] = 1;
--sum, ++ans;
}
}
}
printf("%d\n", ans);
return 0;
}
2、差分约束
路段 \(b\) 到 \(e\) 之间至少要种 \(t\) 棵树 \(e - b \ge t\)
每个路段最多种一棵树,对于路段 \(a\) 和路段 \(a - 1\) 来说 $ 0 \le a - (a - 1) \le 1$
将式子化简开就是 $ (a - 1) - a \ge -1$ 和 \(a - (a - 1) \ge 0\)
关系式有了,又因为是 \(\ge\) ,建图跑最长路
差分约束 代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 3e4 + 5;
const int M = 5e3 + 5;
const int inf = - 1e9 - 5;
int n, m, cnt;
int h[N], dis[N], vis[N], in[N];
struct edge {
int v, nxt;
ll w;
} e[M << 4];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while(ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void add(int u, int v, ll w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = h[u];
h[u] = cnt;
}
ll spfa(int x) {
deque<int> q;
q.push_back(x);
vis[x] = 1;
while(!q.empty()) {
int u = q.front();
q.pop_front();
vis[u] = 0;
for(int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
ll w = e[i].w;
if(dis[v] < dis[u] + w){
dis[v] = dis[u] + w;
++in[v];
if(in[v] > n) return 0;
if(!vis[v]) {
vis[v] = 1;
if(!q.empty() && dis[v] >= dis[q.front()]) {
q.push_front(v);
}
else {
q.push_back(v);
}
}
}
}
}
return dis[n];
}
int main() {
n = read();
m = read();
for(int i = 1; i <= m; ++i) {
int a = read(), b = read();
ll w = read();
add(a - 1, b, w);
}
for(int i = 0; i <= n; ++i) {
if(i) add(i, i-1, -1), dis[i] = inf;
if(i != n) add(i, i + 1, 0);
}
printf("%lld\n",spfa(0));
return 0;
}
[喷水装置]
题目传送门:喷水装置
思路:由于范围是圆,如图:
所以我们要利用勾股定理来求出这个有效范围 贪心忽然成了数学题,我们可以求出一个喷头有效范围的最左端和最右端,按照最左端进行升序排序,以便不会漏下没覆盖到的区域,从左到右一个一个检查,具体看代码吧
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 2e4;
int T, n, l, ans, cnt;
double h, t, r, x;
struct node {
double x, y;
int operator < (const node &b) const {
return x < b.x;
}
} a[N];
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d %d %lf", &n, &l, &h);
cnt = 0;
for (int i = 1; i <= n; ++i) {
scanf("%lf %lf", &x, &r);
if(r <= h/2) continue;
a[++cnt].x = x - (sqrt(r * r - h * h * 0.25));
a[cnt].y = x + (sqrt(r * r - h * h * 0.25));
}
sort(a + 1, a + cnt + 1);
int fg = 1;
t = 0, ans = 0;
while(t < l) {
++ans;
double s = t;
for(int i = 1; i <= cnt && a[i].x <= s; ++i) {
t = max(a[i].y, t);
}
if(s == t && s < l) {
printf("-1\n");
fg = 0;
break;
}
}
if(fg) printf("%d\n", ans);
}
return 0;
}
[加工生产调度]
题目传送门:加工生产调度
思路:第一个产品,在 \(A\) 加工时,没有其他产品在 \(B\) 加工,所以要选 \(a\) 小的,最后一个物品,在 \(B\) 加工时,没有其他产品在 \(A\) 加工,所以要选 \(b\) 小的,对于其他产品,如果该产品的 \(a\) 比 \(b\) 小,那么把这个产品放到前面去,它越早离开A工厂,其他产品就能越早进入A工厂;如果 \(b\) 比 \(a\) 小,那就往后放,它越早离开 \(B\) 工厂,其他产品就能越早进入 \(B\) 工厂。
为什么这么排呢?因为一个物品必须先进入 \(A\) 工厂,在进入 \(B\) 工厂,我们先尽快让所有产品进入 \(A\) 工厂,再尽快让所有产品离开 \(B\) 工厂,这就是我们的贪心策略
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5;
const int inf = ~(1 << 31);
int n, k, t;
int a[N], b[N], m[N], ans[N], s[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
a[i] = read();
}
for (int i = 1; i <= n; ++i) {
b[i] = read();
}
for (int i = 1; i <= n; ++i) {
m[i] = min(a[i], b[i]);
s[i] = i;
}
for(int i = 1; i < n; ++i) {
for(int j = i + 1; j <= n; ++j) {
if(m[i] > m[j]) {
swap(m[i], m[j]);
swap(s[i], s[j]);
}
}
}
k = 0, t = n + 1;
for(int i = 1; i <= n; ++i) {
if(m[i] == a[s[i]]) {
ans[++k] = s[i];
}
else {
ans[--t] = s[i];
}
}
k = 0, t = 0;
for(int i = 1; i <= n; ++i) {
k += a[ans[i]];
if(t < k) {
t = k;
}
t += b[ans[i]];
}
printf("%d\n", t);
for(int i = 1; i <= n; ++i) {
printf("%d ", ans[i]);
}
return 0;
}
[智力大冲浪]
题目传送门:智力大冲浪
思路:
1、搜索 + 剪枝
每个小游戏有选与不选两种结果,如果选,那么总奖金不变,如果主动不选或被迫不选,那么总奖金要减少,我们记录着总奖金的变化,如果总奖金已经比我们找到的最优解小了,那它最后的结果就不可能成为最优解了,最优性剪枝 数据很水,竟然让暴搜 \(+1\) 个小剪枝过了
搜索 + 剪枝 代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 510;
int n, m, maxn;
struct node {
int tim, w;
int operator < (const node &b) const {
return tim == b.tim ? w > b.w : tim < b.tim;
}
} a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void dfs(int cur, int ti, int sum) {
if(sum < maxn) return ;
if(cur == n + 1) {
maxn = max(maxn, sum);
return ;
}
if(ti > a[cur].tim) {
dfs(cur + 1, ti, sum - a[cur].w);
return ;
}
dfs(cur + 1, ti + 1, sum);
dfs(cur + 1, ti, sum - a[cur].w);
}
int main() {
m = read();
n = read();
for (int i = 1; i <= n; ++i) {
a[i].tim = read();
}
for (int i = 1; i <= n; ++i) {
a[i].w = read();
}
sort(a + 1, a + n + 1);
dfs(1, 1, m);
printf("%d\n", maxn);
return 0;
}
2、贪心(正解)
我们按照每个游戏所扣除的钱按降序排序,因为所扣除的钱越大,对答案的影响就越大,我们考虑每个游戏都在最后的限制时间做,那它前面的时间就可以分配给其他游戏了,如果它找到可以占用时间,那这个时间就标记一下,如果它找不到可以占用的时间,那就只能扣钱了
贪心 代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 510;
int n, m;
int vis[N];
struct node {
int ti, w;
int operator < (const node &b) const {
return w > b.w;
}
} a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
m = read();
n = read();
for (int i = 1; i <= n; ++i) {
a[i].ti = read();
}
for (int i = 1; i <= n; ++i) {
a[i].w = read();
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++i) {
int fg = 0;
for(int j = a[i].ti; j; --j) {
if(!vis[j]) {
vis[j] = 1;
fg = 1;
break;
}
}
if(!fg) {
m -= a[i].w;
continue;
}
}
printf("%d\n", m);
return 0;
}
[数列极差]
题目传送门:数列极差
思路:可以发现,按照小数到大数的顺序乘起来可以得到最大值,按照大数到小数的顺序乘起来可以得到最小值,那我们直接用堆来维护顺序,一个大根堆,一个小根堆,每次弹堆顶,插入即可
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
int n;
priority_queue<int> q1;
priority_queue<int, vector <int>, greater<int> > q2;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
int maxn, minn, x, y;
n = read();
for (int i = 1; i <= n; ++i) {
x = read();
q1.push(x);
q2.push(x);
}
while (q2.size() != 1) {
x = q2.top();
q2.pop();
y = q2.top();
q2.pop();
q2.push(x * y + 1);
}
maxn = q2.top();
while (q1.size() != 1) {
x = q1.top();
q1.pop();
y = q1.top();
q1.pop();
q1.push(x * y + 1);
}
minn = q1.top();
printf("%d\n", maxn - minn);
return 0;
}
[数列分段]
题目传送门:数列分段
思路:这个应该算是最简单最基础的贪心了,甚至都不用排序,从前往后扫,一旦和大于 \(m\),再分新的一段
代码:
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
ll n, m, ans, sum;
ll a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while(ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read(), m = read();
for(int i = 1; i <= n; ++i) {
a[i] = read();
}
int l = 1, r = n;
ans = 1;
for(int i = l; i <= r; ++i) {
if(sum + a[i] > m) {
sum = a[i];
++ans;
}
else sum += a[i];
}
printf("%lld\n", ans);
return 0;
}
>[线段]
题目传送门:线段
思路:跟第一题除了题面不同外,基本一模一样,按照每条线段的右端点升序排序,扫一遍即可
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int n;
ll last, ans;
struct node {
ll str, lat;
int operator < (const node &b) const {
return lat < b.lat;
}
} a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while(ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
for(int i = 1; i <= n; ++i) {
a[i].str = read(), a[i].lat = read();
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; ++i) {
if(last <= a[i].str) {
++ans;
last = a[i].lat;
}
}
printf("%lld\n", ans);
return 0;
}
[家庭作业]
题目传送门:家庭作业
思路:与种树那道题思路基本一样,但它的数据范围太大,会超时,所以要再优化一下,当往前扫描时,如果找不到可以占用的时间,那就将这个作业的截至时间记录下来,如果后面的作业截止时间小于等于我们所记录的时间,那他们也一定完不成,直接跳过就行了
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int n, sum, l, fg;
int vis[N];
struct node {
int tim, val;
int operator < (const node &b) const {
return val > b.val;
}
} a[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
for(int i = 1; i <= n; ++i) {
a[i].tim = read();
a[i].val = read();
}
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i) {
fg = 0;
if(a[i].tim < l) continue;
for(int j = a[i].tim; j; --j) {
if(!vis[j]) {
vis[j] = 1;
sum += a[i].val;
fg = 1;
break;
}
}
if(!fg) l = a[i].tim;
}
printf("%d\n", sum);
return 0;
}
[钓鱼]
题目传送门:钓鱼
思路:与堆的应用题 鱼塘钓鱼 很像,但这道题要枚举结束的鱼塘,其他思路基本一样
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 110;
int n, h, sum, maxn;
int tim[N], fish[N], num[N], del[N];
struct node {
int num, w;
int operator < (const node &b) const {
return w < b.w;
}
};
priority_queue<node> q;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read(), h = read();
h *= 12;
for (int i = 1; i <= n; ++i) {
fish[i] = read();
}
for (int i = 1; i <= n; ++i) {
del[i] = read();
}
for (int i = 1; i <= n - 1; ++i) {
int x = read();
tim[i] = tim[i - 1] + x;
}
for(int i = 1; i <= n; ++i) {
sum = 0;
for(int j = 1; j <= i; ++j) {
q.push(node{j, fish[j]});
}
int k = h - tim[i - 1];
for(int j = 1; j <= k; ++j) {
if(q.empty()) break;
node fis = q.top();
q.pop();
int k = fis.w - del[fis.num];
if(k > 0) q.push(node{fis.num, k});
sum += fis.w;
}
maxn = max(maxn, sum);
}
printf("%d\n", maxn);
return 0;
}
[糖果传递]
题目传送门:糖果传递
思路:这个题应该是最难的了,我们设 \(x_i\) 为第 \(i\) 个小朋友向第 \(i - 1\) 个小朋友传递了 \(x_i\) 个糖果,\(x_1\) 为第 \(1\) 个小朋友向第 \(n\) 个小朋友传递了 \(x_1\) 个糖果
每个小朋友最后的糖果数为 \(a_i - x_i + x_{i + 1}\),又因为要获得均等糖果,所以 \(a_i - x_i + x_{i + 1} = \frac{sum}{n}\)
这样的方程有 \(n\) 个,但是最后一个 \(a_n - x_n + x_1\) 是没有意义的,推到这里,\(x_1\) 已经推出来了,可以直接算出来 \(x_n\) 是多少,所以实际上有用的只有前 \(n - 1\) 个公式
这样看肯定无法直接求出 \(x_i\),我们要转换一下,把式子变成 \(x_{i + 1} = \frac{sum}{n} + x_i - a_i\),再转换一下 \(x_i = \frac{sum}{n} + x_{i - 1} - a_i\) ,这样就可以直接求出 \(x_i\) 了。
最后,我们的答案为 $ \left | x_1 \right | + \left | x_2 \right | + \left |x_3 \right | + \left | x_4 \right |\ldots $,我们要让它最小化,取中位数即可.
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int n;
ll sum, ans;
ll a[N], x[N];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
a[i] = read();
sum += a[i];
}
sum = sum / n;
for (int i = 1; i <= n; ++i) {
x[i] = x[i - 1] + sum - a[i - 1];
}
sort(x + 1, x + n + 1);
ll k = x[(n + 1) / 2];
for(int i = 1; i <= n; ++i) {
ans += abs(x[i] - k);
}
printf("%lld\n", ans);
return 0;
}
结束
这里面有很基础的题目,也有很难的题目,笔者贪心部分完成了,接下来就要奋斗分治部分了!