贪心做题笔记

(1) CF1684D Traps

  • n 个数字 a1an 排成一排,你需要从左到右依次越过所有数。

    两种越过 i 的方式:

    1. 花费 ai 的代价越过;
    2. 花费 0 的代价越过,后面的 ai 都加 1

    现在你拥有最多 k 次操作二的机会,求最小的代价总和。

  • n2×105

一定会使用 k 次操作二。否则可以在最后一个使用操作一的位置改用操作二,使答案更优。

假设这 k 次操作二的地方为 i1,i2,,ik,我们考虑其中一个位置 ij 的收益(收益指在 ij 位置由操作一改为操作二后答案会变小多少):

  • 本身的代价 aij 变成了 0,收益增加 aij
  • 位置 ij+1n 中,除了位置 ij+1,ij+2,,ik,代价都会加一(因为它们在跳跃时代价都是 0),收益减少 nij(kj)

综上,总收益为:

j=1k(aijn+ij+kj)

整理得:

nk+k2k(k+1)2+j=1k(aij+ij)

显然我们希望让收益越大越好,所以我们得目标是最大化这个式子的值。

其中 nk+k2k(k+1)2 为定值,我们希望最大化 j=1k(aij+ij)。所以我们将所有值按照 ai+i 排序并取前 k 大即可。

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;

struct FASTREAD {
	template <typename T>
	FASTREAD& operator >>(T& x) {
		x = 0; bool flg = false; char c = getchar();
		while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
		while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
		if (flg) x = -x; return *this;
	}
	template <typename T>
	FASTREAD& operator >>(vector<T>& x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
		return *this;
	}
}fin;

struct FASTWRITE {
	template <typename T>
	FASTWRITE& operator <<(T x) {
		if (x < 0) x = -x, putchar('-');
		static int buf[35]; int top = 0;
		do buf[top ++ ] = x % 10, x /= 10; while (x);
		while (top) putchar(buf[ -- top] + '0');
		return *this;
	}
	FASTWRITE& operator <<(char x) {
		putchar(x); return *this;
	}
	template <typename T>
	FASTWRITE& operator <<(vector<T> x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
		putchar('\n');
		return *this;
	}
}fout;

const int N = 1e6 + 10;
const int P = 998244353;

int res;

void Luogu_UID_748509() {
	int n, k; fin >> n >> k;
	int res = 0;
	vector<int> a(n); fin >> a;
	for (int t : a) res += t;
	res -= -n * k + k * k - k * (k - 1) / 2;
	for (int i = 0; i < n; ++ i ) a[i] += i;
	sort(a.begin(), a.end(), greater<int>());
	for (int i = 0; i < k; ++ i ) res -= a[i];
	fout << res << '\n';
	return;
}

signed main() {
	int Testcases = 1;
	fin >> Testcases;
	while (Testcases -- ) Luogu_UID_748509();
	return 0;
}

(2) CF1029E Tree with Small Distances

  • 给定一颗 n 个节点的树,以 1 为根。

    求最少在树中加几条边,使得任意一点到 1 的距离均小于等于 2

  • n2×105

不难发现最优策略中,每条加的边都有端点 1

第一步,最自然的想法就是将 1最深的叶节点连边。其实不然,最优的策略是连接 1 和叶子节点的父亲。这样能把这个叶子节点的所有兄弟和它父亲的父亲都管控到。

接下来上一步的点就不需要考虑了。我们要做的仍然是连接 1 和最深的点的父亲。如此迭代即可。

实现上,我们可以维护大根堆,以节点的深度从大到小排序。每次取出堆顶即可。

Code
#include <bits/stdc++.h>

using namespace std;

//#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;

struct FASTREAD {
	template <typename T>
	FASTREAD& operator >>(T& x) {
		x = 0; bool flg = false; char c = getchar();
		while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
		while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
		if (flg) x = -x; return *this;
	}
	template <typename T>
	FASTREAD& operator >>(vector<T>& x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
		return *this;
	}
}fin;

struct FASTWRITE {
	template <typename T>
	FASTWRITE& operator <<(T x) {
		if (x < 0) x = -x, putchar('-');
		static int buf[35]; int top = 0;
		do buf[top ++ ] = x % 10, x /= 10; while (x);
		while (top) putchar(buf[ -- top] + '0');
		return *this;
	}
	FASTWRITE& operator <<(char x) {
		putchar(x); return *this;
	}
	template <typename T>
	FASTWRITE& operator <<(vector<T> x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
		putchar('\n');
		return *this;
	}
}fout;

const int N = 1e6 + 10;
const int P = 998244353;

int n;
vector<int> g[N];
int dep[N];
bool st[N];
int fa[N];

void dfs(int u, int f) {
	fa[u] = f;
	for (int v : g[u]) if (v != f) {
		dep[v] = dep[u] + 1;
		dfs(v, u);
	}
	return;
}

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i < n; ++ i ) {
		int x, y; fin >> x >> y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dep[1] = 1;
	int res = 0;
	dfs(1, -1);
	priority_queue<PII> q; 
	for (int i = 1; i <= n; ++ i ) if (dep[i] > 3) q.push({dep[i], i});
	while (q.size()) {
		int u = q.top().second; q.pop();
		if (st[u]) continue;
		u = fa[u];
		st[u] = true;
		for (int v : g[u]) {
			st[v] = true;
		}
		++ res;
	}
	fout << res;
}

signed main() {
	int Testcases = 1;
//	fin >> Testcases;
	while (Testcases -- ) Luogu_UID_748509();
	return 0;
}

(3) CF1197C Array Splitting

  • 给出一个长度为 n 的严格单调增的序列,将其分成 k 段,使得每一段的极差的和最小,求这个最小的和。
  • k,n3×105

推式子。

若这 k 段分别为 [i1,i21],[i2,i31],,[ik,ik+11],其中 i1=1,ik+1=n+1。那么极差和为:

ai21ai1+ai31ai2+ai41ai3++aik+11aik

整理一下,把 +aij1aij 放在一起:

ai1+aik+11+(ai21ai2)+(ai31ai3)++(aik1aik)

其中 ai1+aik+11ana1 是一定的。我们希望让这个式子的值最小,就意味着我们要最小化 (ai21ai2)+(ai31ai3)++(aik1aik)。因此求 di=ai1ai 的前 k1 小即可。

Code
#include <bits/stdc++.h>

using namespace std;

//#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;

struct FASTREAD {
	template <typename T>
	FASTREAD& operator >>(T& x) {
		x = 0; bool flg = false; char c = getchar();
		while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
		while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
		if (flg) x = -x; return *this;
	}
	template <typename T>
	FASTREAD& operator >>(vector<T>& x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
		return *this;
	}
}fin;

struct FASTWRITE {
	template <typename T>
	FASTWRITE& operator <<(T x) {
		if (x < 0) x = -x, putchar('-');
		static int buf[35]; int top = 0;
		do buf[top ++ ] = x % 10, x /= 10; while (x);
		while (top) putchar(buf[ -- top] + '0');
		return *this;
	}
	FASTWRITE& operator <<(char x) {
		putchar(x); return *this;
	}
	template <typename T>
	FASTWRITE& operator <<(vector<T> x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
		putchar('\n');
		return *this;
	}
}fout;

const int N = 1e6 + 10;
const int P = 998244353;

int a[N], d[N];

void Luogu_UID_748509() {
	int n, k; fin >> n >> k;
	for (int i = 1; i <= n; ++ i ) {
		fin >> a[i];
		d[i] = a[i - 1] - a[i];
	}
	sort(d + 2, d + n + 1);
	int res = a[n] - a[1];
	for (int i = 2; i <= k; ++ i ) res += d[i];
	fout << res;
}

signed main() {
	int Testcases = 1;
//	fin >> Testcases;
	while (Testcases -- ) Luogu_UID_748509();
	return 0;
}

(4) CF1038D Slime

  • 给定 n 个数 ai。每次可以选择两个相邻的 ai,ai+1 将其合并为 aiai+1ai+1ai。求 n1 次操作后的数的最大值。
  • n5×105

多手玩几组可以发现,最终的答案一定是对每个 ai 乘上 ±1 的系数后求和。因为题目的操作为 aiai+1ai+1ai,也就是将相邻两个数分别乘上 ±1

所以我们可以对于每个负数乘 1 变成正数,正数乘 1 保持正数,再求和即为答案。其实就是每个数的绝对值之和。

注意会有一个问题。将每个 ai±1 的过程中,不能做到将所有 ai 全部乘相同的系数。所以在所有 ai 同号时贪心选择某个数乘另外一个系数即可。

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;

struct FASTREAD {
	template <typename T>
	FASTREAD& operator >>(T& x) {
		x = 0; bool flg = false; char c = getchar();
		while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
		while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
		if (flg) x = -x; return *this;
	}
	template <typename T>
	FASTREAD& operator >>(vector<T>& x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
		return *this;
	}
}fin;

struct FASTWRITE {
	template <typename T>
	FASTWRITE& operator <<(T x) {
		if (x < 0) x = -x, putchar('-');
		static int buf[35]; int top = 0;
		do buf[top ++ ] = x % 10, x /= 10; while (x);
		while (top) putchar(buf[ -- top] + '0');
		return *this;
	}
	FASTWRITE& operator <<(char x) {
		putchar(x); return *this;
	}
	template <typename T>
	FASTWRITE& operator <<(vector<T> x) {
		for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
		putchar('\n');
		return *this;
	}
}fout;

const int N = 1e6 + 10;
const int P = 998244353;

void Luogu_UID_748509() {
	int n, res = 0; fin >> n;
	vector<int> a(n); fin >> a;
	if (n == 1) {
		fout << a[0];
		return;
	}
	sort(a.begin(), a.end());
	int mn = a[0], mx = a[n - 1];
	if (mn >= 0) {
		res = -a[0];
		for (int i = 1; i < n; ++ i ) res += a[i];
	}
	else if (mx <= 0) {
		res = a[n - 1];
		for (int i = 0; i + 1 < n; ++ i ) res -= a[i];
	}
	else {
		for (int t : a) res += abs(t);
	}
	fout << res;
	return;
}

signed main() {
	int Testcases = 1;
//	fin >> Testcases;
	while (Testcases -- ) Luogu_UID_748509();
	return 0;
}

(5) CF804A Find Amir

  • 有一张 n 个节点的完全图,其中连接 i,j 两点的边的边权为 (i+j)mod(n+1)。求走完所有城市所需的最小花费(起点任选)。
  • n105

方案是 1n2(n1)3,边权分别为 0,1,0,1,

所以答案为边数的一半,即 n12

Code
#include <bits/stdc++.h>

using namespace std;

int main() {
    int n; cin >> n;
    cout << (n - 1) / 2;
}

(6)

给定 n 个区间 [ai,bi]。若将所有有交集的区间合并,最后有多少区间。

ai 排序。记录当前正在尝试合并的区间的左右端点 l,r。初始 l=a1,r=b1

枚举 i=(2,3,,b)。此时:

  • 如果 rai:合并。实现上就是 l 不变,rmax(r,bi)
  • 如果 r<ai:重开。实现上就是 lairbi

(7)

给定 n 个区间 [ai,bi]

给定 m 个人,每个人有一个区间 [ci,di]ki

如果 [ai,bi][cj,dj] 包含,那么第 j 个人就可以选择区间 i

每个区间至多被一个人选择,每个人至多选 ki 个区间。

求是否所有区间都能被某个人选中。

把区间和人放在一起,并全部以左端点排序。左端点相同时把人排在前面。

然后按顺序枚举 i=(1,2,,n+m)。如果第 i 个是区间,那么我们需要找到一个人 j 且满足:

  • ajci
  • bjdi

由于我们是按 a,c 排序的,所以第一个条件即 j<i。问题也就变成了要在 i 之前找到一个人 j,且人的右端点在区间的右端点的右面。

此时如果有多个满足条件的人,那么我们应该贪心地找右端点最小的,也就是找 bj 最小的。

实现上维护 set。枚举到人时加入 (di,ki)。枚举到区间时找到最小的大于等于 bidj,并将 kj 减一。如果 kj=0 将其移除 set

(8)

给定长度为 n 的序列 a

对于一个 x,选择 ai 的代价为 |aix|

给定代价之和的上限 B。求一个 x,使得选择的 ai 最多。

可以发现,如果将 a 从小到大排序,那么最终选择的 ai 一定是连续的。

若确定了区间 [l,r],那么代价之和为 f(l,r)=i=lr|aix|。很显然将 x 确定为这些数的中位数 f(l,r) 最小。

枚举左端点 l。此时需要找到最大的 r 满足 f(l,r)B。此时可以 O(nlogn) 二分做。

同时可以发现随着 l 变大,r 一定不会变小。所以可以 O(n) 双指针。

(9)

给定长度为 n 的序列 a

至多 m 次操作,每次可以将一个大于 0ai 减一。

你需要进行若干次操作后,使得存在 ak=0,且 max(|aiai+1|) 最小。

求这个最小值。

二分答案 x。然后求在不一定存在 ak=0 的条件,所有的 |aiai+1| 都小于等于 x 的最小操作次数。

先从左往右调整 aimin(ai,ai1+x)。然后还会有不满足的,于是再从右往左调整 aimin(ai,ai+1+x)。记录操作次数。

然后枚举 k=(1,2,,n) 表示将 ak0。那么序列会变成这样:

a1,,l×x,,2x,x,0,x,2x,,r×x,,an

也就是说在 ak 出形成了一个辐射状的等差数列,这个等差数列往左延申到 kl,往右延申到 k+r

可以发现随着 k 的递增,klk+r 都不会变小。所以还是双指针。

(10)

给定 n 个区间 [ai,bi]

你需要选择 m 个区间,使得它们至少有一个公共点且区间长度的极差最小。

如果有若干个区间被选中,那么如果其中有 m 个区间有至少一个公共点,就代表如果将每个区间内的位置加 1 的话存在一个位置大于等于 m

我们首先按照区间长度从小到大排序。然后枚举最小长度 l,找到最大长度 r,使得将 lr 这些区间按照上述操作加一后存在一个位置大于等于 m。那么此时 rl 即为答案。

找这个 r 可以二分做。同时观察到,随着 l 变大,r 一定不会变小。所以 O(n) 双指针。

(11)

给定长度为 n 的仅含 ab 的字符串。

你可以改变至多 k 个字符,求改变后最大的连续相同字符的数量。

二分答案 x。要做的就是判断是否存在一个长度为 x 的区间,使得能够在进行不超过 k 次的情况下,将其变成相同的。于是统计区间内 ab 字符分别数显的次数,然后与 k 比大小即可。

也可以双指针。假设我们要全变成 a,那么仍然是维护两个指针 l,r,表示 lrb 的数量不超过 k。于是找到最大的 r 然后计算最大长度 rl+1 即可。

(12)

给定长度为 n 的序列 a

定义 f(l,r) 表示区间 [l,r] 内不同元素的数量。

随机选取 l,r,求 f(l,r) 的期望。

一个区间内相同的元素只会对答案产生一次贡献。不妨将这次贡献放在最左面的元素上。

考虑这个问题:第 i 个元素上一次出现在 j 的位置,有多少个区间 [l,r]i 元素可以贡献的?

显然需要满足两个条件:

  • j<li
  • irn

那么答案即 (ij)×(ni+1)

于是统计每个元素在它之前最后一次出现的位置。然后根据上述公式累加答案即可。

posted @   2huk  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示