贪心算法题目合集2

贪心算法题目合集2

紧接上文贪心算法题目合集-CSDN博客

因为不同的题贪心策略可能完全不同,它们的共同点是做决策时往往只看局部最优。这里按照个人理解划分。

完善了很多细节,比如Johnson算法的证明、排序规律的分析过程等。

一般排序

这类题用sort自带的反函数(小于)就能解决。

排队接水

1319:【例6.1】排队接水

求怎么做才能让每人的平均接水时间最少,用贪心策略的话,就是谁接水用的时间最少谁就先接水 。同时所有人接水时,每个人总共消耗的时间是等待时间接水时间,等待时间是之前的人接水用的时间,这个可以用递推式去计算。另外最后一个人的接水时间并不算进总的等待时间里。

例如这个样例:

复制代码
5
1 2 3 4 5
//递推式
接水时间:1 2 3 4 5
实际耗时:1 3 6 10 15
等待时间:0 1 4 10 20

到这里,这题的思路就很清晰了:先排序,再用递推式去计算等待时间,最后求平均值即可。

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<algorithm>
#include<iomanip>
using namespace std;

struct info {
	int tim;
	int ord;
	info(int a = 0, int b = 0) {//构造函数初始化
		tim = a; ord = b;
	}
};
info t[1001];
bool cmp(info a, info b) {
	return a.tim < b.tim;
}

int main() {
	int n;
	double sum = 0;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> t[i].tim;
		t[i].ord = i;
	}
	sort(t + 1, t + n + 1, cmp);//STL的sort函数,底层是快排
	for (int i = 1; i <= n; i++) {
		cout << t[i].ord << " ";
        
        //之前的那个人的接水时间就是现在这个人的等待时间
		sum += double(t[i - 1].tim); 
		t[i].tim += t[i - 1].tim;
	}
	cout << endl;
	cout << fixed << setprecision(2) << (sum / n);//保留2位小数输出
	return 0;
}

整数区间

1324:【例6.6】整数区间

题目要求找到一个集合,使得所有区间至少有1个点,都能在这个集合上找到。既然是集合,所以不要求连续。

站在人的视角,将所有区间放在数轴上展示,能更直观地展示。例如这个样例:

复制代码
4
3 6
2 4
0 2
4 7

可以很直观地看到,区间[2,4]正好包含所有区间的至少有1个点。

因此就有了贪心策略:

  1. 按区间右端点排序。
  2. 给一个初始变量x=-1表示上一个区间的右端点,选-1是为了能将第1个区间也进行统一计算。
  3. 枚举每个区间a[i]的左端点,若a[i]左端点<=x则说明该区间在划定的集合中有端点,否则将左端点加如集合(形式上的加入,并不记录),并更新xa[i]这个区间的右端点。

参考程序:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

void ac(){
	vector<pair<int,int> >a;
	int n;
	cin>>n;a.resize(n+1,{0,0});
	for(int i=1;i<=n;i++)
		cin>>a[i].second>>a[i].first;//为方便排序,交换区间左、右端点
	sort(a.begin()+1,a.end());
	
	int len=0,x=-1;
	for(int i=1;i<=n;i++){
		if(a[i].second<=x)
			continue;
		len++;//加一个点
		x=a[i].first;
	}
	cout<<len;
}

int main() {
	ac();
	return 0;
}

金银岛

1225:金银岛

这题需要将所有金属的单位价格进行枚举,然后进行贪心策略:装单位价值最大的金属,直到装不下时就切割还没选过的单位价值最大的金属来填。

参考程序:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main() {
	int T;
	cin >> T;
	while (T--) {
		int w, n;
		cin >> w >> n;
		vector<pair<double, pair<int, int> > >a(n + 1, {0, { 0,0 } });
		//单位价格、重量和总价

		for (int i = 1; i <= n; i++) {
			cin >> a[i].second.first >> a[i].second.second;
			a[i].first = a[i].second.second * 1.0 / a[i].second.first;
		}
		sort(a.begin() + 1, a.end());
		double ans = 0;
		for (int i = n; i >= 1; i--) {
			if (w >= a[i].second.first) {
				w -= a[i].second.first;
				ans += a[i].second.second;
			}
			else {
				ans += w * a[i].first;
				break;
			}
		}
		printf("%.2lf\n", ans);
	}
}

寻找平面上的极大点

1230:寻找平面上的极大点

  1. 将所有点排序。
  2. 逆向枚举所有点,对每个点,再在之前的点找支配的点,找到了就标记。
  3. 正向遍历所有的点,对没有做过标记的点输出即可。
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main() {
	int n;
	cin >> n;
	vector<pair<int, int> >a(n + 1, pair<int, int>());//匿名对象初始化
	for (int i = 1; i <= n; i++)
		cin >> a[i].first >> a[i].second;
	sort(a.begin() + 1, a.end());

	vector<bool>used(n + 1, 0);
	for (int i = n; i >= 1; i--) {
		if (used[i]) continue;
		for (int j = 1; j < i; j++) {
			if (used[j])
				continue;
			if (a[j].first <= a[i].first&& a[j].second <= a[i].second)
				used[j] = 1;
		}
	}
	for (int i = 1; i < n; i++) {
		if (!used[i])
			cout << "(" << a[i].first << "," << a[i].second << "),";
	}
	cout << "(" << a[n].first << "," << a[n].second << ")";
	return 0;
}

P1056 [NOIP 2008 普及组\] 排座椅 - 洛谷](https://www.luogu.com.cn/problem/P1056) 因为横向通道和纵向通道互不影响,所以可以分别处理横向通道和纵向通道。 因为同一个一维坐标可能有多个交头接耳的同学,所以需要坐标和数量进行绑定,可以用结构体或类表示。 ```cpp #include using namespace std; struct Inf { int index = 0; int cnt = 0; }; void ac() { int n, m, k, l, d; cin >> m >> n >> k >> l >> d; vectorH(m+1), L(n+1); for (int i = 1; i <= m; i++) H[i].index = i; for (int i = 1; i <= n; i++) L[i].index = i; for (int i = 1; i <= d; i++) { int x1, x2, y1, y2; cin >> x1 >> y1 >> x2 >> y2; if (x1 == x2) L[min(y1, y2)].cnt++; else H[min(x1, x2)].cnt++; } sort(H.begin() + 1, H.end(), [&](Inf& a, Inf& b) {return a.cnt > b.cnt; }); sort(L.begin() + 1, L.end(), [&](Inf& a, Inf& b) {return a.cnt > b.cnt; }); sort(H.begin() + 1, H.begin()+k+1, [&](Inf& a, Inf& b) {return a.index < b.index; }); sort(L.begin() + 1, L.begin()+l+1, [&](Inf& a, Inf& b) {return a.index < b.index; }); for (int i = 1; i <= k; i++) cout << H[i].index << ' '; cout << endl; for (int i = 1; i <= l; i++) cout << L[i].index << ' '; } int main() { int T = 1; //cin>>T; while (T--) ac(); return 0; } ``` ### NOIP 2008 普及组 排座椅 \[P1056 [NOIP 2008 普及组\] 排座椅 - 洛谷](https://www.luogu.com.cn/problem/P1056) 给两个数组`H`和`L`,分别记录第`i`条横道或竖道的交头接耳的同学对数`num`,同时记住和`num`绑定的下标`i`。 之后给的一对坐标`(x,y)`和`(p,q)`,若`x==p`,说明在`min(y,q)`竖道的交头接耳的同学多了一对,反之同理。 之后先对`H`和`L`数组的`num`进行降序排序,再对`H`和`L`的前`k`个和前`l`个数据的和`num`绑定的下标`i`进行降序排序,再输出即可。 ```cpp #include using namespace std; struct P { int num = 0; int i = 0; }; //仿函数 struct Big { template bool operator()(T& a, T& b) { return a.num > b.num; } }; struct Sml { template bool operator()(T& a, T& b) { return a.i < b.i; } }; void ac() { int m, n, k, l, d; cin >> m >> n >> k >> l >> d; vector

H(m + 1), L(n + 1); for (int i = 1; i <= d; i++) { int x, y, p, q; cin >> x >> y >> p >> q; if (x == p) { int mi = min(y, q); L[mi].num++; L[mi].i = mi; } else { int mi = min(x, p); H[mi].num++; H[mi].i = mi; } } sort(H.begin() + 1, H.end(), Big()); sort(L.begin() + 1, L.end(), Big()); sort(H.begin() + 1, H.begin() + 1 + k, Sml()); sort(L.begin() + 1, L.begin() + 1 + l, Sml()); for (int i = 1; i <= k; i++) cout << H[i].i << ' '; cout << endl; for (int i = 1; i <= l; i++) cout << L[i].i << ' '; cout << endl; } int main() { int T = 1; //cin >> T; while (T--) ac(); return 0; } ``` ## 推导排序规律 这类题就是寻找排序规则,排序就是在该排序规则下对整个对象排序。 在解决某些问题的时,当我们发现最终结果**需要调整每个对象的先后顺序**,也就是对整个对象排序时,那么我们就可以用推公式的方式,得出我们的排序规则,进而对整个对象排序。 正确性证明: 利用排序解决问题,最重要的就是需要证明"在新的排序规则下,整个集合可以排序"。这需要用到离散数学中"全序关系"的知识。我会在第一道题中证明该题的排序规则下,整个集合是可以排序的。 但是证明过程很麻烦,后续题目中我们只要发现该题最终结果**需要排序** ,并且**交换相邻两个元素的时候** ,**对其余元素不会产生影响**,那么我们就可以推导出排序的规则,然后直接去排序,就不去证明了。 ### NOIP 1998 提高组 拼数 \[P1012 [NOIP 1998 提高组\] 拼数 - 洛谷](https://www.luogu.com.cn/problem/P1012) [拼数](https://www.luogu.com.cn/problem/P1012)这题用`string`数组存储数据,对数组按照`sort`自带的仿函数(大于)进行排序,只有2个样例不过不过。这种按照原始字典序进行排序的思路可以举例出反例:53和534。 假设两个数`a`,`b`,例如73,345。按照字典序,73 \> 345,因此73345 \> 34573,而且字典序已经有反例53、534。 因此需要更换策略。两个数只有两种拼接方法`ab`或`ba`。 * 若`ab > ba`,则`a`放`b`前。 * 若`ab < ba`,则`b`放`a`前。 * 若`ab == ba`,则顺序无所谓。 2个数可以按照这个规则,多个也可以。 这个题的本质是确定所有数的先后顺序,很容易想到排序。 以选择排序为例,对比所有的数,谁小就放第1个位置,之后重复同样的对比,直到所有数都对比完,这也可以说用到了某种贪心策略。 所以这个题也可以利用排序算法,结合拼接`ab`或`ba`进行比对的比较方式,确定最终的先后顺序。 但贪心策略不一定是正确的,且这个排序规则不一定正确,衍生的话就是什么样的排序规则才适合用于排序,分析放在后文。 ```cpp #include using namespace std; void ac() { int n; cin >> n; vectorst; for (int i = 0; i < n; i++) { string x; cin >> x; st.push_back(x); } sort(st.begin(), st.end(), [&](string& a, string& b) {return a + b < b + a; }); for (size_t i = st.size() - 1; i != -1; i--) cout<> T; while (T--) ac(); return 0; } ``` ### 排序规则的正确性证明:全序关系 当定义新的排序规则时,这个规则并不一定能用于排序。 例如3个数据 { a , b , c } \\{a,b,c\\} {a,b,c}, a ≥ b a\\geq b a≥b,且 b ≥ c → a ≥ c b\\geq c\\rightarrow a\\geq c b≥c→a≥c,则说明这3数据的比较规则具有传递性。若推不出这种传递性,即 a ≤ c a\\leq c a≤c,则这种规则时不能用来排序的。例如 `{石头,剪刀,步}`就不能排序。 对朴素的数据进行排序时,即整数和浮点数都是有这种传递性的,所以排序时并不会特意证明这个比价规则。 离散数学的知识点全序关系:假设一个集合`{a,b,c,d,e,f,g}`,从集合中任选2个元素,它们满足某种比较规则下,符合一个全序关系,就说整个集合可以排序的。这个特性可以用于证明排序规则可用。 只要从集合中任选2个元素,满足这3个性质,则说明这个集合有全序关系。 1. 完全性。即某种规则能用于比大小。 2. 反对称性。即两个元素 a a a、 b b b,在某种规则下 a ≤ b a\\leq b a≤b且 b ≤ a b\\leq a b≤a,则 a = = b a==b a==b。 或某种规则下 a a a在前 b b b在后,且 b b b前 a a a后,则应该有 a a a、 b b b的顺序无所谓。若排序规则不满足这个点(即 a a a、 b b b和 b b b、 a a a有差异),则不能对 a a a、 b b b背后的集合排序。 3. 传递性。即 a ≥ b a\\geq b a≥b,且 b ≥ c → a ≥ c b\\geq c\\rightarrow a\\geq c b≥c→a≥c。 或 a a a前 b b b后,且 b b b前 c c c后,则 a a a前 c c c后。 拼数这个OJ的结论: * 若`ab > ba`,则`a`放`b`前。 * 若`ab < ba`,则`b`放`a`前。 * 若`ab == ba`,则顺序无所谓。 证明拼数的结论有全序关系的3个性质:、 设 a a a是 x x x位的数, b b b是 y y y位的数, a b ab ab( a a a和 b b b进行拼接)相当于 a b = 10 y × a + b ab=10\^{y}\\times a+b ab=10y×a+b, 则 b a = 10 x × b + a ba=10\^{x}\\times b+a ba=10x×b+a。 1. 证明完全性。 0在平时是不算几位数的,否则在某些推论是错误的。例如0是1位数,则00应该是最小的2位数,但实际最小的2位数是10。 但在这里,当 a = b = 0 a=b=0 a=b=0时,在这里0当成1位数。例如1和0拼接,则 10 = 10 1 × 1 + 0 10=10\^{1}\\times 1+0 10=101×1+0, 01 = 10 1 × 0 + 1 01=10\^1\\times 0+1 01=101×0+1,这样才能保证 a b = 10 y × a + b ab=10\^{y}\\times a+b ab=10y×a+b和 b a = 10 x × b + a ba=10\^{x}\\times b+a ba=10x×b+a是正确的。 当 a a a、 b b b拼接时, a b ab ab和 b a ba ba的长度相同,无论是数值还是字典序方面都能比大小(完全性的结论),所以证毕。 2. 证明反对称性。 a b ≤ b a ab\\leq ba ab≤ba且 a b ≤ b a ab\\leq ba ab≤ba,则 a b = b a ab=ba ab=ba 将公式代入: 10 y × a + b ≥ 10 x × b + a 10\^{y}\\times a+b\\geq 10\^{x}\\times b+a 10y×a+b≥10x×b+a, 10 x × b + a ≥ 10 y × a + b 10\^{x}\\times b+a\\geq 10\^{y}\\times a+b 10x×b+a≥10y×a+b, 这俩能推出 a b ≥ b a ≥ a b ab\\geq ba \\geq ab ab≥ba≥ab,根据夹逼准则(见高等数学),得到 a b = b a ab=ba ab=ba。 于是 10 y × a + b = 10 x × b + a 10\^{y}\\times a+b = 10\^{x}\\times b+a 10y×a+b=10x×b+a,证毕。 3. 证明传递性。 对3个数 a a a、 b b b、 c c c, 若 a b ≥ b a ab\\geq ba ab≥ba且 b c ≥ c b bc\\geq cb bc≥cb,则能推出 a c ≥ c a ac\\geq ca ac≥ca,即 a a a、 b b b、 c c c这样的顺序。 假设 a a a、 b b b、 c c c分别是 x x x、 y y y、 z z z, 则 10 y × a + b ≥ 10 x × b + a 10\^{y}\\times a+b\\geq 10\^{x}\\times b+a 10y×a+b≥10x×b+a, 10 z × b + c ≥ 10 y × c + b 10\^{z}\\times b+c\\geq 10\^{y}\\times c+b 10z×b+c≥10y×c+b, 对 10 y × a + b ≥ 10 x × b + a 10\^{y}\\times a+b\\geq 10\^{x}\\times b+a 10y×a+b≥10x×b+a,将 a a a移到左边, b b b移到右边,得到 ( 10 y − 1 ) × a 10 x − 1 ≥ b \\frac{(10\^y-1)\\times a}{10\^x-1}\\geq b 10x−1(10y−1)×a≥b。 对 10 z × b + c ≥ 10 y × c + b 10\^{z}\\times b+c\\geq 10\^{y}\\times c+b 10z×b+c≥10y×c+b,将 b b b移到左边, c c c移到右边,得到 ( 10 y − 1 ) × c 10 z − 1 ≤ b \\frac{(10\^y-1)\\times c}{10\^z-1}\\leq b 10z−1(10y−1)×c≤b。 ( 10 y − 1 ) × a 10 x − 1 ≥ b \\frac{(10\^y-1)\\times a}{10\^x-1}\\geq b 10x−1(10y−1)×a≥b和 ( 10 y − 1 ) × c 10 z − 1 ≤ b \\frac{(10\^y-1)\\times c}{10\^z-1}\\leq b 10z−1(10y−1)×c≤b能得到 ( 10 y − 1 ) × a 10 x − 1 ≥ ( 10 y − 1 ) × c 10 z − 1 \\frac{(10\^y-1)\\times a}{10\^x-1}\\geq \\frac{(10\^y-1)\\times c}{10\^z-1} 10x−1(10y−1)×a≥10z−1(10y−1)×c。 分母约去 ( 10 y − 1 ) (10\^y-1) (10y−1)并交叉相乘得到 ( 10 z − 1 ) × a ≥ ( 10 x − 1 ) × c (10\^z-1)\\times a\\geq (10\^x-1)\\times c (10z−1)×a≥(10x−1)×c。 再来看这个式子 a c ≥ c a ac\\geq ca ac≥ca,代入公式得到 10 z × a + c ≥ 10 x × c + a 10\^z\\times a+c\\geq 10\^x\\times c+a 10z×a+c≥10x×c+a, a a a移到左边, c c c移到右边,可得到: ( 10 z − 1 ) × a ≥ ( 10 x − 1 ) × c (10\^z-1)\\times a\\geq (10\^x-1)\\times c (10z−1)×a≥(10x−1)×c,说明结论若 a b ≥ b a ab\\geq ba ab≥ba且 b c ≥ c b bc\\geq cb bc≥cb,则 a c ≥ c a ac\\geq ca ac≥ca正确,说明这个比较规则具有传递性。 综上,这个比价规则满足全序关系,能用于比较大小。 ### 证明拼数的贪心策略正确 目标是将整个数组排序,假设这个数组的贪心解是 `[...,a,...,b,...]`,交换`a`和`b`,得到 `[...,b,...,a,...]`, 证明`[...,b,...,a,...]`一定不优于`[...,a,...,b,...]`即可证明贪心策略正确,即`[...,a,...,b,...]>=[...,b,...,a,...]`。 假设`a`、`b`中间的元素分别是`[a,x,y,z,b]`,通过传递性逐步调整成`[b,x,y,z,a]`: `a` 前,`x`后,根据全序关系的结论, a x ≥ x a ax \\geq xa ax≥xa, 所以交换顺序后变成`[x,a,y,z,b]`,因为 a x ≥ x a ax \\geq xa ax≥xa,所以 `[a,x,y,z,b]>=[x,a,y,z,b]`; 对`[x,a,y,z,b]`,交换`a`和`y`也就有 `[x,a,y,z,b]>=[x,y,a,z,b]`,则 a y ≥ y a ay\\geq ya ay≥ya, 因为 a x ≥ x a ax \\geq xa ax≥xa, x y ≥ y x xy\\geq yx xy≥yx,所以 a y ≥ y a ay\\geq ya ay≥ya,所以这3的顺序是 a a a、 x x x、 y y y。 根据这个思路,可以调整到最后:`[x,y,z,b,a]`, 用同样的思路也可以将`b`调整到第1位,得到`[b,x,y,z,a]`。 但调整过程中`[a,x,y,z,b]>=[x,a,y,z,b]>=[x,y,a,z,b]>=...>=[b,x,y,z,a]`,说明这种调整方式只会使最后的整数变小或相等,所以交换两个元素并不能得到比贪心解更优的解,贪心策略正确。 ### P2878 \[USACO07JAN\] Protecting the Flowers S \[P2878 [USACO07JAN\] Protecting the Flowers S - 洛谷](https://www.luogu.com.cn/problem/P2878) 最终结果是确定牵牛的顺序,因此需要对所有的牛进行排序。 思路1: 假设2奶牛 i i i、 j j j,拉走它们要用的时间分别是 t i t_i ti、 t j t_j tj,它们在单位时间的吃花量是 d i d_i di、 d j d_j dj。交换它们被牵走的顺序并不影响其他奶牛。 假设牵走 i i i、 j j j左边的奶牛用时 T 1 T_1 T1,这段时间吃掉的花 D 1 D_1 D1,右边的则是 T 2 T_2 T2, D 2 D_2 D2 ![请添加图片描述](https://i-blog.csdnimg.cn/direct/8299f092869b4e44aa22294c3734733b.png) 假设 i i i先被牵走, j j j后被牵走是最优解,总的吃花数 s u m i = D 1 + D 2 + T 1 × d i + ( T 1 + 2 × t i ) × d j sum_i=D_1+D_2+T_1\\times d_i+(T_1+2\\times t_i)\\times d_j sumi=D1+D2+T1×di+(T1+2×ti)×dj, j j j先被牵走, i i i后被牵走,总的吃花数 s u m j = D 1 + D 2 + T 1 × d j + ( T 1 + 2 × t j ) × d i sum_j=D_1+D_2+T_1\\times d_j+(T_1+2\\times t_j)\\times d_i sumj=D1+D2+T1×dj+(T1+2×tj)×di, 则 s u m i \< s u m j sum_i\ d b t b \\frac{d_a}{t_a}\>\\frac{d_b}{t_b} tada\>tbdb时,优先牵走 a a a牛是最优解。 因为 t t t, b b b都是整型,直接除的话会舍弃小数部分造成精度损失,所以通过交叉相乘转换一下: d a × t b \> d b × t a d_a\\times t_b\>d_b\\times t_a da×tb\>db×ta。这个不等式恰好就是思路1的结论。 ```cpp #include using namespace std; typedef long long LL; struct cmd { template bool operator()(T& a, T& b) { return a.first * b.second < a.second* b.first; } }; void ac() { LL n; cin >> n; LL sum = 0; vector >a(n + 1, { 0,0 }); for (int i = 1; i <= n; i++) { cin >> a[i].first >> a[i].second; sum += a[i].second; } LL res = 0; sort(a.begin() + 1, a.end(), cmd()); for (auto& tmp : a) { sum -= tmp.second; res += sum * tmp.first * 2; } cout << res; } int main() { int T = 1; //cin >> T; while (T--) ac(); return 0; } ``` ### P1842 \[USACO05NOV\] 奶牛玩杂技 - 洛谷 \[P1842 [USACO05NOV\] 奶牛玩杂技 - 洛谷](https://www.luogu.com.cn/problem/P1842) 首先读题目得到的信息: 1. 牛牛们玩叠罗汉,每头牛都有向下的重力和向上的推力,每头牛的推力减去它受到的重力,即为这头牛的压扁指数;所有牛的压扁指数的最大值,即为整个牛群的压扁指数的最大值。 2. 确定一个顺序,来使这个最大值最小。 首先这个最大值最小很容易误解成二分,但这里并不需要枚举某个有序数据。 和前2个题一样,假设某种最优顺序中,有2头牛分别是第`i`头和第`j`头,交换它们的顺序,对它们下方的牛和上方的牛的压扁指数没有影响(即两头牛的重力不变),但会对中间的牛造成影响。 就算如此,依旧可以用类似冒泡排序的思路,依次比较相邻两头牛交换前和交换后,两头牛互相给对方带来的压扁指数的变化。 即交换前的`j`受到`i`的重力影响产生的压扁指数`w[i]-s[j]`,和交换后的`w[j]-s[i]`,若交换前的`w[i]-s[j]`小于交换后的`w[j]-s[i]`,则说明这两头牛的位置安排合理。通过2头牛的局部最优扩展为全局最优,即可得到贪心解。 因此可以用`w[i]-s[j] using namespace std; struct cmd { typedef pairpii; bool operator()(pii& a, pii& b) { return a.second - b.first < b.second - a.first; } }; void ac() { int n; cin >> n; vector >a(n + 1); for (int i = 1; i <= n; i++) { cin >> a[i].first >> a[i].second; } sort(a.begin() + 1, a.end(), cmd()); int res = -2e9, w=0; for (int i = 1; i <= n; i++) { res = max(w - a[i].second, res); w += a[i].first; } cout << res; } int main() { int T = 1; //cin >> T; while (T--) ac(); return 0; } ``` 这里的思路是只算两头牛分别对对方造成的影响,但如果是从全局考虑: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/5434f8ccfbbd438f91395df954315029.png) 对表达式`max(-si,wi-sj)wi-sj`,根据数学规律,`wi-sj>-sj`,因此 `-si>wi-sj>-sj`,这种情况下原不等式只能取`-siwj-si`,根据数学规律,`wj-si>-si`,因此 `-sj>wj-si>-si`,这种情况下原不等式能取`-sj>wi-sj`或`-sj>-si`,此时若 `-si>wi-sj`则根据1,不等式恒成立,所以`-sj>wi-sj>-sj`,矛盾。 根据1、2的分析,表达式不能取到`-si`和`-sj`作为不等式两边的最后结果,因此表达式被简化成`wi-sj j i\>j i\>j),则 T ( S , t ) = a i + T ( S − { J i } , b i + max ⁡ { t − a i , 0 } ) T(S,t)=a_i + T(S - \\{J_i\\},b_i + \\max\\{t - a_i,0\\}) T(S,t)=ai+T(S−{Ji},bi+max{t−ai,0}) = a i + a j + T ( S − { J i , J j } , b j + max ⁡ { b i + max ⁡ { t − a i , 0 } − a j , 0 } ) =a_i + a_j + T(S - \\{J_i,J_j\\},b_j + \\max\\{b_i + \\max\\{t - a_i,0\\} - a_j,0\\}) =ai+aj+T(S−{Ji,Jj},bj+max{bi+max{t−ai,0}−aj,0}) > S − { J i , J j } S - \\{J_i,J_j\\} S−{Ji,Jj}表示排除 J i J_i Ji和 J j J_j Jj后剩下的工作, b i + max ⁡ { t − a i , 0 } b_i + \\max\\{t - a_i,0\\} bi+max{t−ai,0}表示之前的作业消耗的时间,所以 b j + max ⁡ { b i + max ⁡ { t − a i , 0 } − a j b_j + \\max\\{b_i + \\max\\{t - a_i,0\\}-a_j bj+max{bi+max{t−ai,0}−aj表示 J j J_j Jj可能的等待时间。 = a i + a j + T ( S − { J i , J j } , T i j ) =a_i + a_j + T(S - \\{J_i,J_j\\},T_{ij}) =ai+aj+T(S−{Ji,Jj},Tij) 其中 T i j = b j + max ⁡ { b i + max ⁡ { t − a i , 0 } − a j , 0 } T_{ij} = b_j + \\max\\{b_i+\\max\\{t - a_i,0\\} - a_j,0\\} Tij=bj+max{bi+max{t−ai,0}−aj,0} = b i + b j − a j + max ⁡ { max ⁡ { t − a i , 0 } , a j − b i } =b_i + b_j - a_j + \\max\\{\\max\\{t - a_i,0\\},a_j - b_i\\} =bi+bj−aj+max{max{t−ai,0},aj−bi} > T i j = b j + max ⁡ { b i + max ⁡ { t − a i , 0 } − a j , 0 } − b i + b i + a j − a j T_{ij} = b_j + \\max\\{b_i+\\max\\{t - a_i,0\\} - a_j,0\\}-b_i+b_i+a_j-a_j Tij=bj+max{bi+max{t−ai,0}−aj,0}−bi+bi+aj−aj,目的是消去 max ⁡ \\max max列表内的多项式中多余的单项式。 = b i + b j − a j + max ⁡ { t − a i , a j − b i , 0 } =b_i + b_j - a_j + \\max\\{t - a_i,a_j - b_i,0\\} =bi+bj−aj+max{t−ai,aj−bi,0} = b i + b j − a i − a j + max ⁡ { t , a i , a i + a j − b i } =b_i + b_j - a_i - a_j + \\max\\{t,a_i,a_i + a_j - b_i\\} =bi+bj−ai−aj+max{t,ai,ai+aj−bi} > 同样的操作,等式同时加一个 a i a_i ai和减一个 a i a_i ai。 = { t + b i + b j − a i − a j , 若 max ⁡ { t , a i , a i + a j − b i } = t b i + b j − a j , 若 max ⁡ { t , a i , a i + a j − b i } = a i b j , 若 max ⁡ { t , a i , a i + a j − b i } = a i + a j − b i (1) =\\begin{cases} t+b_i+b_j-a_i-a_j,\\quad\\ \\ 若\\max\\{t,a_i,a_i+a_j-b_i\\}=t\\\\ b_i+b_j-a_j, \\ \\quad\\quad\\quad\\quad\\quad若\\max\\{t,a_i,a_i+a_j-b_i\\}=a_i\\\\ b_j,\\quad \\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\ 若\\max\\{t,a_i,a_i+a_j-b_i\\}=a_i+a_j-b_i \\end{cases} \\tag{1} =⎩ ⎨ ⎧t+bi+bj−ai−aj, 若max{t,ai,ai+aj−bi}=tbi+bj−aj, 若max{t,ai,ai+aj−bi}=aibj, 若max{t,ai,ai+aj−bi}=ai+aj−bi(1) 若按作业 J i J_i Ji和作业 J j J_j Jj的加工顺序调换,则有: T ′ ( S , t ) = a i + a j + T ( S − { J i , J j } , T j i ) T'(S,t)=a_i + a_j + T(S - \\{J_i,J_j\\},T_{ji}) T′(S,t)=ai+aj+T(S−{Ji,Jj},Tji),其中根据上面的推导, T j i = b i + b j − a i − a j + max ⁡ { t , a j , a i + a j − b j } T_{ji} = b_i + b_j - a_i - a_j + \\max\\{t,a_j,a_i + a_j - b_j\\} Tji=bi+bj−ai−aj+max{t,aj,ai+aj−bj} 按假设,因为 T ≤ T ′ T \\leq T' T≤T′成立,所以有: max ⁡ { t , a i + a j − b i , a i } ≤ max ⁡ { t , a i + a j − b j , a j } ⋯ ① \\max\\{t,a_i + a_j - b_i,a_i\\} \\leq \\max\\{t,a_i + a_j - b_j,a_j\\}\\cdots ① max{t,ai+aj−bi,ai}≤max{t,ai+aj−bj,aj}⋯① > 由 T ≤ T ′ → max ⁡ { t , a i + a j − b i , a i } ≤ max ⁡ { t , a i + a j − b j , a j } T\\leq T'\\rightarrow \\max\\{t,a_i + a_j - b_i,a_i\\}\\leq \\max\\{t,a_i + a_j - b_j,a_j\\} T≤T′→max{t,ai+aj−bi,ai}≤max{t,ai+aj−bj,aj},消去了 − a i , − a j , b i , b j -a_i,-a_j,b_i,b_j −ai,−aj,bi,bj。 于是有: a i + a j + max ⁡ { − b i , − a j } ≤ a i + a j + max ⁡ { − b j , − a i } a_i + a_j + \\max\\{-b_i,-a_j\\}\\leq a_i + a_j + \\max\\{-b_j,-a_i\\} ai+aj+max{−bi,−aj}≤ai+aj+max{−bj,−ai} 即 min ⁡ { b j , a i } ≤ min ⁡ { b i , a j } ⋯ ② \\min\\{b_j,a_i\\}\\leq\\min\\{b_i,a_j\\} \\cdots ② min{bj,ai}≤min{bi,aj}⋯② 针对 ② ② ②表达式的所有情况: 1. a i \< b i a_i\ a j a_i\>a_j ai\>aj, * 因为 a j \< a i \< b i a_j\ a j a_i\>a_j ai\>aj,所以无论怎么取,和假设 a i \> a j a_i\>a_j ai\>aj矛盾。 假设 b i ≤ b j b_i\\leq b_j bi≤bj, * 则 a i \< b i ≤ b j a_i\ a j b_i\>a_j bi\>aj,而 b i \> a i b_i\>a_i bi\>ai,依旧无法确定 a i a_i ai和 a j a_j aj的大小,但可以猜: * 若 a i ≤ a j a_i\\leq a_j ai≤aj,则这个不等式合理; * 而若 a i \> a j a_i \>a_j ai\>aj,则只能取 b i b_i bi,但 a i \< b i a_i\ b j b_i\>b_j bi\>bj, * 则 b i \> b j \> a j b_i\>b_j\>a_j bi\>bj\>aj,所以 ② ② ②变成 min ⁡ { b j , a i } ≤ a j \\min\\{b_j,a_i\\}\\leq a_j min{bj,ai}≤aj, * 因为 a j \< b j a_j\ b i b_j\> b_i bj\>bi,矛盾。 假设 b i ≥ b j b_i\\geq b_j bi≥bj, * 因为 b j ≤ b i ≤ a i b_j\\leq b_i\\leq a_i bj≤bi≤ai,所以 ② ② ②变成 b j ≤ min ⁡ { b i , a j } b_j\\leq \\min\\{b_i,a_j\\} bj≤min{bi,aj}, * 因为 a j ≥ b j a_j\\geq b_j aj≥bj,所以只能取 b j ≤ b i b_j\\leq b_i bj≤bi,也就是条件。 假设 a i ≤ a j a_i\\leq a_j ai≤aj, * 因为 b i ≤ a i ≤ a j b_i\\leq a_i \\leq a_j bi≤ai≤aj,所以 ② ② ②变成 min ⁡ { b j , a i } ≤ b i \\min\\{b_j,a_i\\}\\leq b_i min{bj,ai}≤bi, * 因为 a i ≥ b i a_i\\geq b_i ai≥bi,所以只能取 b j ≤ b i b_j\\leq b_i bj≤bi。 假设 a i \> a j a_i\>a_j ai\>aj, * 因为 b j ≤ a j \< a i b_j\\leq a_j\ a i b_j\>a_i bj\>ai,则 ② ② ②变成 a i ≤ min ⁡ { b i , a j } a_i\\leq \\min\\{b_i,a_j\\} ai≤min{bi,aj}, * 若 a j ≥ a i a_j\\geq a_i aj≥ai,则因为 b i \> a j b_i\> a_j bi\>aj,所以 a i ≤ min ⁡ { b i , a j } a_i\\leq \\min\\{b_i,a_j\\} ai≤min{bi,aj}成立; * 若 a j \< a i a_j\ a i a_j\\geq b_j\>a_i aj≥bj\>ai,和 a j \< a i a_j\ a j b_i\>a_j bi\>aj,此时 min ⁡ { b j , a i } = min ⁡ { b i , a j } \\min\\{b_j,a_i\\}=\\min\\{b_i,a_j\\} min{bj,ai}=min{bi,aj}成立的条件是 a i = a j a_i=a_j ai=aj。即2个作业在机器A中加工的时间相等。 * a i \> b j a_i\>b_j ai\>bj且 b i ≤ a j b_i\\leq a_j bi≤aj,此时 min ⁡ { b j , a i } = min ⁡ { b i , a j } \\min\\{b_j,a_i\\}=\\min\\{b_i,a_j\\} min{bj,ai}=min{bi,aj}成立的条件是 b j = b i b_j=b_i bj=bi,即2个作业在B中加工的时间相等。 * a i \> b j a_i\>b_j ai\>bj且 b i \> a j b_i\>a_j bi\>aj,此时 min ⁡ { b j , a i } = min ⁡ { b i , a j } \\min\\{b_j,a_i\\}=\\min\\{b_i,a_j\\} min{bj,ai}=min{bi,aj}成立的条件是 b j = a j b_j=a_j bj=aj,即作业 j j j在两个机器的加工时间相等。 综上,满足这4种情况的作业,也满足等式 min ⁡ { b j , a i } = min ⁡ { b i , a j } \\min\\{b_j,a_i\\}=\\min\\{b_i,a_j\\} min{bj,ai}=min{bi,aj}。则这些作业的安排: 1. 若 i i i、 j j j属于 a i \< b i a_i\ b j b_i\>b_j bi\>bj,此时 i i i和 j j j依旧可以划分成同一阵营,但先加工 i i i再加工 j j j的顺序不会变。 综上,当 min ⁡ { b j , a i } = min ⁡ { b i , a j } \\min\\{b_j,a_i\\}=\\min\\{b_i,a_j\\} min{bj,ai}=min{bi,aj}时,按照 a i ≤ a j a_i\\leq a_j ai≤aj来划分顺序,无论 i i i、 j j j在哪个阵营或同在一个阵营,都不会对整体时间造成影响。 综合上述分析,作业 S S S的每个作业先在A机器中加工,再到B机器中加工,整体用时最短的策略是: 将 S S S的作业分成2个集合,集合 N 1 N_1 N1的每个作业满足 a \< b a\ 回顾Johnson算法内容:设 N 1 N_1 N1为 a \< b a\B`,则B车间出现等待时间,则进行时间同步,也就是`B=A`。 对`A`和`B`的处理参考这张图: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/3358845eff284a8fb82778e6ce0a0a1b.png) 思路2: 1. 选出每个工作在两个车间用时的最小值,用另外的数组`arrange`存储。 2. 对`arrange`数组按照记录的最小时间进行升序排序。 3. 利用双指针向内收缩安排工作顺序。如果最小时间是在A车间的用时,则安插在左指针,左指针右移;否则安插在右指针,右指针左移。 4. 和思路1一样的模拟。 参考程序: ```cpp #include using namespace std; void ac1() { struct Area { int at = 0; int bt = 0; int ord = 0; Area(int a, int b, int o) :at(a), bt(b), ord(o) {} }; int n; cin >> n; vectora(n, { 0,0,0 }); for (int i = 0; i < n; i++) cin >> a[i].at, a[i].ord = i; for (int i = 0; i < n; i++) cin >> a[i].bt; sort(a.begin(), a.end(), [&](Area& x, Area& y) { //直接套用Johnson算法的结论 if (min(x.at, y.bt) == min(x.bt, y.at)) return x.at < y.at;//最小值相同时按照在A机器中的加工时间安排顺序 return min(x.at, y.bt) < min(x.bt, y.at); }); int times = 0, A = 0, B = 0; for (auto& x : a) { A += x.at; if (B < A) B = A; B += x.bt; } cout << B << endl; for (auto& x : a) { cout << x.ord + 1 << ' '; } } void ac2() { struct Area1 {//a,b表示在a,b车间完成作业的时间 int a; int b; Area1(int _a = 0, int _b = 0) :a(_a), b(_b) {} }; vectorwork;//作业 struct Area2 {//times表示作业id在哪个车间的完成时间最短,times记录那个时间 int times; int id; Area2(int _a = 0, int _b = 0) :times(_a), id(_b) {} }; vectorarrange;//安排 int n; cin >> n; work.resize(n + 1, Area1()); arrange.resize(n + 1, Area2()); for (int i = 1; i <= n; i++) cin >> work[i].a; for (int i = 1; i <= n; i++) cin >> work[i].b; //Johnson算法 //记录每个作业在哪个车间的完成时间最短 for (int i = 1; i <= n; i++) { arrange[i].times = min(work[i].a, work[i].b); arrange[i].id = i; } sort(arrange.begin() + 1, arrange.end(), [&](Area2& a, Area2& b) {return a.times < b.times; }); vectorans(n + 1, 0);//最终安排顺序 int l = 0, r = n + 1; for (int i = 1; i <= n; i++) { if (arrange[i].times == work[arrange[i].id].a) ans[++l] = arrange[i].id; else ans[--r] = arrange[i].id; } l = 0, r = 0;//代码复用,l表示所有作业在A加工的时间 for (int i = 1; i <= n; i++) { l += work[ans[i]].a; if (r < l)//计算B车间的等待时间 r = l; r += work[ans[i]].b; } cout << r << endl; for (int i = 1; i < ans.size(); i++) cout << ans[i] << ' '; } int main() { int T = 1; //cin>>T; while (T--) { ac1(); //ac2(); } return 0; } ``` ## 带点思维 ### 均分纸牌(Noip2002) [1320:【例6.2】均分纸牌(Noip2002)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1320) 这题的最终目的是让若有堆的牌相等。 所以可以反其道而行:先求得所有堆的平均数,知道最后所有堆的牌数是多少,然后枚举除了最后一堆的所有堆,牌数不符合要求就对右边的牌进行操作即可。 参考程序: ```cpp #include using namespace std; void ac(){ int n;cin>>n; vectora(n+1,0); for(int i=1;i<=n;i++){ cin>>a[i]; a[0]+=a[i]; } a[0]/=n; int times=0; for(int i=1;ia[0]){ a[i+1]=a[i+1]+(a[i]-a[0]); a[i]=a[0]; times++; } } cout< using namespace std; void ac(){ string st;int s; cin>>st>>s; //删到最后是升序,后面是\0,可以删最末尾 for(int i=0;i=0,s--;st.erase(i,1)){//结束条件:所有牌都删完。 while(i1&&st[0]=='0') st.erase(0,1); cout<>T;//提交到1321删数问题时将这一句给注释掉 while(T--) ac(); return 0; } ``` 经验证,猜想正确,两个OJ[1321:【例6.3】删数问题(Noip1994)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1321)、[1231:最小新整数](http://ybt.ssoier.cn:8088/problem_show.php?pid=1231)都能AC。证明略,或现有水平暂时无法证明。 ### 装箱问题 OJ:[1226:装箱问题](http://ybt.ssoier.cn:8088/problem_show.php?pid=1226) 因为高度都是`h`,所以主要还是看平面。 主要的包装方法: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/0ceb5d92796347598a17eddec1f8e056.png) 也就是说 6 × 6 6\\times6 6×6、 5 × 5 5\\times5 5×5和 4 × 4 4\\times4 4×4的产品要单独用一个包裹,一个包裹最多能装4个 3 × 3 3\\times3 3×3的产品。 所以就有了贪心策略: 1. 优先装底边为6、5、4的产品。 2. 底边为3的产品分4个一组,每组用一个包裹。多出来的根据情况选择需要用到的底边为2的产品数。 3. 统计用底边为2的产品填满底边为4和底边为3的包裹的空隙需要的数量,如果多了则用另外的包裹来填。 4. 底边为1的产品的需求可以通过总的底面积减去已经用掉的底面积得到。 参考程序: ```cpp #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS 1 #endif #include using namespace std; int main() { int a[7] = { 0 }; while (1) { a[0] = 0; for (int i = 1; i <= 6; i++) { cin >> a[i]; a[0] += a[i]; } if (!a[0]) return 0; int ans = a[6] + a[5] + a[4] + ceil(a[3] / 4.0);//ceil会对浮点数向上取整 int tmp = 0; if (a[3] % 4 == 3) tmp = 1; if (a[3] % 4 == 2) tmp = 3; if (a[3] % 4 == 1) tmp = 5; int num2 = 5 * a[4] + tmp;//底边为2的产品的需求 if (num2 < a[2]) { ans = ans + ceil((a[2] - num2) / 9.0); } int num1 = 36 * ans - 25 * a[5] - 16 * a[4] - 9 * a[3] - 4 * a[2]; if (num1 < a[1]) { ans = ans + ceil((a[1] - num1) / 36.0); } cout << ans << endl; } } ``` ### Ride to Office [1227:Ride to Office](http://ybt.ssoier.cn:8088/problem_show.php?pid=1227) 我在做这题的时候首先想到的是追击相遇问题,但后来发现测试样例对不上,因为测试样例直接就是最快的那个人的用时。 还有就是提早出发的人,若比晚出发的人快则会提早到终点;若比晚出发的人慢则会被追上。所以无论什么情况都不考虑他们。 因此这题的贪心策略:枚举所有晚出发的人,找到他们骑完整个路的最短时间即可。 ```cpp #include using namespace std; int main() { int n; while (cin >> n && n) { int v, t; int mmin = 0x3f3f3f3f; for (int i = 1; i <= n; i++) { cin >> v >> t; if (t >= 0) { mmin=min(mmin,(int)ceil(4500 * 3.6 / v + t)); } } cout << mmin << endl; } return 0; } ``` ### 电池的寿命 [1229:电池的寿命](http://ybt.ssoier.cn:8088/problem_show.php?pid=1229) 虽然电池不可切割,但电量可以。 所以可以尝试贪心策略: 1. 统计所有电池的总电量`sum`,并找出电量最大的电池`maxx`。 2. 若电量最大的电池,占总电量的一半以上,则所有电池能用的时间就是`sum-maxx`。 3. 否则就是先用其他电池拼凑出电量为`maxx`的虚拟电池,将电量为`maxx`的电池的电量耗尽;再将剩下的电池的总电量对半分,所以所有电池能用的时间就是`maxx+(sum-maxx*2)/2`。 ```cpp #include using namespace std; int main() { int n; vectora; while (cin >> n) { a.resize(n + 1, 0); int sum = 0,maxx=0; for (int i = 1; i <= n; i++) { cin >> a[i]; sum += a[i]; maxx = max(maxx, a[i]); } if (sum - maxx <= sum / 2) { printf("%.1lf\n",(double)sum - maxx); continue; } printf("%.1lf\n",(sum - 2.0 * maxx) / 2 + maxx); } return 0; } ``` ### Crossing River [1232:Crossing River](http://ybt.ssoier.cn:8088/problem_show.php?pid=1232) 这个题有两种贪心策略: 1. 大佬带萌新。即用时最短的那个人,帮最慢的两个人过河。 2. 假设1、2是过河最快的2人,他们先过河,然后1回;然后最慢的两人过河,2回。 因为不知道哪个策略能得到最优解,于是做一个前瞻预测: 1. 所有人的过河时间进行排序。 2. 针对最慢的两个人,分别计算两种贪心策略的用时,选择用时最小的那个策略。直到只剩3人或不到3个人。 3. 最后只剩3人,1带3过河,1回,1带2过河。 最后只剩2人或1人,直接过河即可。 ```cpp #include using namespace std; int main() { int _n; cin >> _n; while (_n--) { int n; cin >> n; vectora(n + 1, 0); int vi = 0; for (int i = 1; i <= n; i++) cin >> a[i]; sort(a.begin() + 1, a.end()); int times = 0; while (n > 3) { int tmp1 =a[2] + a[1] + a[n] +a[2];//1、2过河,1回,n和n-1再过,2回 int tmp2 = 2 * a[1] + a[n] + a[n - 1];//大佬带萌新 times += min(tmp1, tmp2); n -= 2; } if (n == 3) times += a[3] + a[2] + a[1]; if (n == 2) times += a[2]; if (n == 1) times += a[1]; cout << times << endl; } return 0; } ``` ### 接水问题 [1233:接水问题](http://ybt.ssoier.cn:8088/problem_show.php?pid=1233) 水龙头有多个,可以先安排`m`个人接水,然后枚举`[m+1,n]`的人到`m`个队列中用时最短的队列,最后在`m`个队列中找出用时最大的那个队列即可。 这题很明显可以用dfs加剪枝或堆优化,但测试样例只认目光短浅的贪心策略,所以私以为这是作者故意考思维安排的题。 ```cpp #include using namespace std; int main() { int n, m; cin >> n >> m; vectora(n + 1, 0); for (int i = 1; i <= n; i++) cin >> a[i]; int mini = 1; //m个水龙头都被占用 for (int i = m + 1; i <= n; i++) { mini = 1; for (int j = 1; j <= m; j++)//找谁先接完水 if (a[j] < a[mini]) mini = j; a[mini] += a[i]; } mini = 0; for (int i = 1; i <= n; i++) mini = max(a[i], mini); cout << mini; return 0; } ``` ## 动态规划 枚举(搜索)、贪心和动态规划是三种常见的解决最优解问题的方法。一般数据量不大时可以枚举,稍微大一点时可以尝试贪心。但贪心因为目光短浅问题有时无法得到最优解,便需要动态规划。 ### 拦截导弹(NOIP1999) [1322:【例6.4】拦截导弹问题(Noip1999)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1322) 分析样例: 389 207 155 300 299 170 158 65 可以知道,这组样例需要2套拦截系统。第1套拦截`{389 207 155}`,第2套拦截`{300 299 170 158 65}`。 因此可以尝试给出贪心策略进行模拟: 按原顺序枚举导弹,对每枚导弹有两种情况: 1、能被现有的系统给拦截,拦截成功后更新该系统的最大拦截高度。 2、不能被现有的系统给拦截,则再新增一套系统。 [1322:【例6.4】拦截导弹问题(Noip1999)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1322)参考程序: ```cpp #include using namespace std; void ac(){ vectorgm,sys;//导弹、系统 //读取全部的导弹信息 for(int i=0;cin>>i;gm.push_back(i)); for(int i=0;i=gm[i]){ x=gm[i]; flag=1; break; } } if(flag)//假设不成立,跳过 continue; else//假设成立,新增一套系统 sys.push_back(gm[i]); } cout< using namespace std; typedef long long ll; void ac() { int n; cin >> n; vectora(n+1,0); for (int i = 1; i <= n; i++) cin >> a[i]; ll res = 0, ans = -0x3f3f3f3f3f3f3f3f; for (int i = 1; i <= n; i++) { res += a[i]; ans = max(res, ans); if (res < 0) res = 0; } cout << ans << endl; } int main() { ac(); return 0; } ``` 尝试证明: 在某一段`[a,b]`,子段和`<0`,则对任意`a<=c&&c<=b`,都不可能是最优解。 反证法:假设存在点`c`在区间`[a,b]`,累加到不大于`b`时或累加到大于`b`时能找到最优解。记`[a,b]`的字段和为`sum[a,b]`, 1. 累加到不大于`b`时就能找到最优解。 假设`k`属于`[c,b]`,则`sum[c,k]>sum[a,k]`,这样的话`sum[a,c]<0`,而我们的贪心策略是`sum[a,b]<0`,所以`sum[a,c]<0`和`sum[a,b]<0`的结论矛盾,所以假设不成立。 2. 累加到大于`b`时能找到最优解。 假设`k>=b`,`sum[c,k]`是最优解,则应该有`sum[c,b]>sum[a,b]`,但是满足`sum[c,b]>sum[a,b]`需要先满足`sum[a,c]<0`,与贪心策略矛盾,所以假设不成立。 所以贪心策略是正确的。 ## 枚举 ### An Easy Problem [1223:An Easy Problem](http://ybt.ssoier.cn:8088/problem_show.php?pid=1223) 因为范围已经被固定,所以直接从给定数开始枚举,每枚举到1个数便拆解成二进制统计1的个数,和给定数相同就停止枚举,更换下一个数;否则继续枚举。 ```cpp #include #include #include #define endl "\n" using namespace std; int binary(int& n) { int tmp = n; int cnt = 0; while (tmp) { if (tmp % 2) ++cnt; tmp /= 2; } return cnt; } int main() { int n = 0; while (cin >> n, n != 0) { int cnt = binary(n); for (int i = n + 1;; i++) { if (cnt == binary(i)) { cout << i << endl; break; } } } return 0; } ``` ### 矩阵消除游戏 二进制枚举 [矩阵消除游戏](https://ac.nowcoder.com/acm/problem/200190) 因为每消除一行都会对列造成影响,所以只能先枚举行的消除方法,枚举时可以通过二进制枚举,详细见枚举。 若消除行之后次数`k`还有剩余,则对每列还剩的元素进行求和,将求和后的数据进行排序,选最大的列数进行相加即可。 开始我连列都用二进制枚举,后来超时才想到用贪心。 ```cpp #include using namespace std; typedef long long ll; int calc(int x) { int cnt = 0; while (x) { ++cnt; x &= (x - 1); } return cnt; } void ac() { int n, m, k; cin >> n >> m >> k; vector >pct(n, vector(m, 0)); vectorcol(m, 0), tmp; ll ssum = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> pct[i][j]; } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { col[i] += pct[j][i]; } } tmp = col; ll res = 0; for (int i = 0; i < (1 << n); i++) { int num = calc(i); if (num > k) continue; col = tmp; ll sum = 0; for (int j = 0; j < n; j++) { if ((i >> j) & 1) { for (int kk = 0; kk < m; kk++) { sum += pct[j][kk]; col[kk] -= pct[j][kk]; } } } sort(col.begin(), col.end()); num = k - num; for (size_t j = col.size() - 1; num > 0 && j != -1; j--, num--) { sum += col[j]; } res = max(res, sum); } cout << res; } int main() { int T = 1; //cin >> T; while (T--) ac(); return 0; } ``` ## 贪心算法OJ汇总 1. 绝对值不等式 [P10452 货仓选址 - 洛谷](https://www.luogu.com.cn/problem/P10452) \[P2512 [HAOI2008\] 糖果传递 - 洛谷](https://www.luogu.com.cn/problem/P2512) 2. 交换论证法的模板题 \[P1094 [NOIP 2007 普及组\] 纪念品分组 - 洛谷](https://www.luogu.com.cn/problem/P1094) 除了这题,很多区间问题都能用交换论证法证明贪心策略的正确性 3. 堆的应用 [【模板】哈夫曼编码](https://ac.nowcoder.com/acm/problem/233601) [1228:书架](http://ybt.ssoier.cn:8088/problem_show.php?pid=1228) [1427:数列极差](http://ybt.ssoier.cn:8088/problem_show.php?pid=1427) [P1717 钓鱼 - 洛谷](https://www.luogu.com.cn/problem/P1717) 4. 区间问题 [P1803 凌乱的yyy / 线段覆盖 - 洛谷](https://www.luogu.com.cn/problem/P1803) [P1250 种树 - 洛谷](https://www.luogu.com.cn/problem/P1250) [1424:【例题3】喷水装置](http://ybt.ssoier.cn:8088/problem_show.php?pid=1424) [UVA1193 Radar Installation - 洛谷](https://www.luogu.com.cn/problem/UVA1193) \[P2887 [USACO07NOV\] Sunscreen G - 洛谷](https://www.luogu.com.cn/problem/P2887) \[P2859 [USACO06FEB\] Stall Reservations S - 洛谷](https://www.luogu.com.cn/problem/P2859)(这题也用到了堆优化) 5. 带期限和罚款的单位时间任务调度 [1426:【例题5】智力大冲浪](http://ybt.ssoier.cn:8088/problem_show.php?pid=1426) [1430:家庭作业](http://ybt.ssoier.cn:8088/problem_show.php?pid=1430) 6. 一般排序 [1319:【例6.1】排队接水](http://ybt.ssoier.cn:8088/problem_show.php?pid=1319) [1324:【例6.6】整数区间](http://ybt.ssoier.cn:8088/problem_show.php?pid=1324) [1225:金银岛](http://ybt.ssoier.cn:8088/problem_show.php?pid=1225) [1230:寻找平面上的极大点](http://ybt.ssoier.cn:8088/problem_show.php?pid=1230) \[P1056 [NOIP 2008 普及组\] 排座椅 - 洛谷](https://www.luogu.com.cn/problem/P1056) 7. 推导排序规律 \[P1012 [NOIP 1998 提高组\] 拼数 - 洛谷](https://www.luogu.com.cn/problem/P1012) \[P2878 [USACO07JAN\] Protecting the Flowers S - 洛谷](https://www.luogu.com.cn/problem/P2878) \[P1842 [USACO05NOV\] 奶牛玩杂技 - 洛谷](https://www.luogu.com.cn/problem/P1842) 8. Johnson算法解决流水作业调度问题 [P1248 加工生产调度 - 洛谷](https://www.luogu.com.cn/problem/P1248) 9. 带点思维的模拟题 [1320:【例6.2】均分纸牌(Noip2002)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1320) [P1106 删数问题 - 洛谷](https://www.luogu.com.cn/problem/P1106) [1226:装箱问题](http://ybt.ssoier.cn:8088/problem_show.php?pid=1226) [1227:Ride to Office](http://ybt.ssoier.cn:8088/problem_show.php?pid=1227) [1229:电池的寿命](http://ybt.ssoier.cn:8088/problem_show.php?pid=1229) [1232:Crossing River](http://ybt.ssoier.cn:8088/problem_show.php?pid=1232) [1233:接水问题](http://ybt.ssoier.cn:8088/problem_show.php?pid=1233) 10. 动态规划 [1322:【例6.4】拦截导弹问题(Noip1999)](http://ybt.ssoier.cn:8088/problem_show.php?pid=1322) [1282:最大子矩阵](http://ybt.ssoier.cn:8088/problem_show.php?pid=1282) [P1115 最大子段和 - 洛谷](https://www.luogu.com.cn/problem/P1115) 11. 枚举 [1223:An Easy Problem](http://ybt.ssoier.cn:8088/problem_show.php?pid=1223) [矩阵消除游戏](https://ac.nowcoder.com/acm/problem/200190)

相关推荐
2301_7944615712 分钟前
力扣-有效三角形的个数
数据结构·算法·leetcode
Tiny番茄16 分钟前
LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetCode 131.分割回文串
算法·leetcode·职场和发展
zhangpz_19 分钟前
【数据结构】树状数组
数据结构·算法·树状数组
悲伤小伞22 分钟前
C++_数据结构_哈希表(hash)实现
数据结构·c++·笔记·算法·哈希算法·散列表
ab_dg_dp29 分钟前
C++ inline 内联函数
c++
贺函不是涵1 小时前
【沉浸式求职学习day46】【华为5.7暑期机试题目讲解】
学习·算法·华为
June`1 小时前
FloodFill算法:洪水般的图像处理艺术
算法·深度优先
珂朵莉MM1 小时前
2023 睿抗机器人开发者大赛CAIP-编程技能赛-本科组(国赛) 解题报告 | 珂学家
人工智能·算法·职场和发展·深度优先·图论
JANYI20182 小时前
C语言经典面试题及答案100道
linux·c语言·c++·算法·ubuntu·面试