郑州大学2024年寒假训练 Day2:循环,一维数组,排序,贪心(A-J)

这场主要还是贪心,后面那几道贪心还是相当典型的。值得一写。


A

洛谷原题 B3673 [语言月赛202210] 垃圾分类

思路

没什么好说的,第 i i i 种垃圾能放进第 i i i 个垃圾桶就放,放不下再放万能垃圾桶。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e6+6;
typedef long long ll;

int n,c,r[maxn];

int main(){
	cin>>n>>c;
	for(int i=1;i<=n;i++)
		cin>>r[i];
	ll ans=0;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		if(t>r[i])ans+=1ll*(t-r[i])*c;
	}
	cout<<ans;
	return 0;
}

B

洛谷原题 P1093 [NOIP2007 普及组] 奖学金

思路:

排序题,先按总分从高到低排序,再按语文成绩从高到低排序,最后按学号从小到大排序 的一个三关键字排序。

一般排序就用sort就可以了,它默认降序排列,某些题需要用到特定排序的性质时可能需要手写排序,比如求逆序对,求第k大数。sort最常规的使用方式就是对基本数据类型进行排序,但这远远不够,我们需要学会使用重载或者函数对象实现自定义的排序方法。

sort函数是依靠比较来实现的排序算法(大部分排序算法是通过比较实现,少部分则不是,比如桶排序,基数排序),因此如果要实现自定义的排序方法,实际上只需要我们确定元素之间的大小关系(“小”数放在前面,“大”数放在后面)。

code:

下面介绍三种实现方式(23不是那么必要):

  1. 手写排序函数:
    手写一个bool函数,作为sort函数进行元素大小比较时用到的函数,然后作为sort函数第三个参数(这个相当于传了个函数指针)。
    我的记法就是口诀:大于从大到小,小于从小到大,如果需要多关键字排序,那么在上一个关键字不相等时才使用上一个关键字排序,相等时则尝试使用下一个关键字排序,具体可以看代码。
    cmp(a,b)相当于把a<b的位置换成了它的函数体的内容,cmp函数为真时a排在前面,b排在后面,反之a排在后面,b排在前面。在最终的序列中,两两元素之间都满足cmp为真。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=305;

int n;
struct student{
	int yu,tot,id;
}a[maxn];

bool cmp(student a,student b){
	if(a.tot!=b.tot)return a.tot>b.tot;
	if(a.yu!=b.yu)return a.yu>b.yu;
	return a.id<b.id;
} 

int main(){
	cin>>n;
	for(int i=1,x,y,z;i<=n;i++){
		cin>>x>>y>>z;
		a[i].id=i;
		a[i].tot=x+y+z;
		a[i].yu=x;
	}
	sort(a+1,a+n+1,cmp);
	
	for(int i=1;i<=5;i++){
		printf("%d %d\n",a[i].id,a[i].tot);
	}
	return 0;
}
  1. 重载小于号,cmp(a,b)与a.operator<(b)(也就是a<b)是等价的,cmp函数里怎么写,重载小于号函数里就怎么写。在结构体中第一个隐含的参数就是调用这个函数的对象本身,所以传一个参即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=305;

int n;
struct student{
	int yu,tot,id;
	bool operator<(student x){
		if(tot!=x.tot)return tot>x.tot;
		if(yu!=x.yu)return yu>x.yu;
		return id<x.id;
	}
}a[maxn];

int main(){
	cin>>n;
	for(int i=1,x,y,z;i<=n;i++){
		cin>>x>>y>>z;
		a[i].id=i;
		a[i].tot=x+y+z;
		a[i].yu=x;
	}
	sort(a+1,a+n+1);
	
	for(int i=1;i<=5;i++){
		printf("%d %d\n",a[i].id,a[i].tot);
	}
	return 0;
}
  1. 函数对象,函数对象的范畴比较广,一般来说只要能实现一个类似于函数的功能,即传入几个参数,返回一个值,它就可以叫做函数对象。比如上面第一种实现方式的函数指针。函数对象与lambda表达式
    这里说两个,一个是传入对象,另一个是传入lambda表达式,它们都是函数对象。
    传入对象:sort(a+1,a+n+1,greater<int>())。这里greater<int>()创建了一个临时的greater<int>对象,并把这个对象传入了sort函数中,这个对象重载了()运算符,使得对象可以像函数一样使用。这个greater<int>实现的功能是把sort函数改为对int元素的降序排序,理论上来说,只要重载了()运算符,它就可以当作函数对象实现自定义排序手段。
    lambda表达式:lambda表达式本质上是被编译器处理成一个重载了()运算符的类对象,实现机理和上面一致。它的写法比较简便,简单来说就是[](){},方括号里写捕获参数,圆括号里写传入参数,花括号里写函数体即可。
sort(a+1,a+n+1,[](student a,student b){
	if(a.tot!=b.tot)return a.tot>b.tot;
	if(a.yu!=b.yu)return a.yu>b.yu;
	return a.id<b.id;
});

C

洛谷原题 P1223 排队接水

思路:

比较经典的贪心,可以很容易想到一个贪心策略:接水时间短的人先接。

接下来证明:假如按接水时间升序排列得到每个人的节水时间为 t 1 , t 2 , . . . , t x , . . . , t y , . . . , t n t_1,t_2,...,t_x,...,t_y,...,t_n t1,t2,...,tx,...,ty,...,tn

如果交换其中 x x x y y y 两个人更优的话,那么交换 t x t_x tx t y t_y ty ,看等待时间如何变化。发现 x x x 前面和 y y y 后面的人的等待时间没有变化,而 x x x y y y 之间的人从原来每人等 t x t_x tx 变成了等 t y t_y ty,显然不优,与假设矛盾。类似的,如果把接水时间更长的人与前面接水时间更短的人交换,中间的人等待的时间就会变长,因此一定是接水时间短的人在前。

实际上贪心策略大部分情况下都不太好证明,所以赛时基本都是手玩一下数据,如果感觉没有问题就可以试一试。

策略找到了,只需要排好序后统计一下总的等待时间即可。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1005;

int n;
pair<int,int> t[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>t[i].first,t[i].second=i;
	sort(t+1,t+n+1);
	
	long long tot=0;
	for(int i=1;i<=n;i++){
		tot+=1ll*(n-i)*t[i].first;
		printf("%d ",t[i].second);
	}
	printf("\n%.2lf",1.*tot/n);
	return 0;
}

D

洛谷原题 P4995 跳跳!

思路:

很容易想到一个贪心思路:从最低点跳到最高点,然后跳到次低点,再跳到次高点…后面类推。

证明有些难度,不妨假设地面高度是 h 0 = 0 h_0=0 h0=0,那么题目相当于找到一个路径 h 0 → h p → h q → . . . → h x h_0 \rightarrow h_p \rightarrow h_q \rightarrow ... \rightarrow h_x h0hphq...hx ,经过每个点一次,路线长度最长。

不妨先看从 h 0 h_0 h0 跳到 h n h_n hn 是否最优,如果不优,那么应当存在一个点 p p p,使得从 h 0 h_0 h0 移动到 h p h_p hp更优。那么不妨让 h n h_n hn h p h_p hp 之间的路径翻转一下,也就是从 h 0 → h n → . . . → h p ‾ → h q → . . . → h x h_0 \rightarrow \underline{h_n \rightarrow ... \rightarrow h_p} \rightarrow h_q \rightarrow... \rightarrow h_x h0hn...hphq...hx变为 h 0 → h p → . . . → h n ‾ → h q → . . . → h x h_0 \rightarrow \underline{h_p \rightarrow ... \rightarrow h_n} \rightarrow h_q \rightarrow... \rightarrow h_x h0hp...hnhq...hx看看这时代价产生了什么变化。

  1. 如果 x ≠ p x\not=p x=p ,也就是p后面有后继节点。发现只有 h 0 → h n h_0 \rightarrow h_n h0hn h p → h q h_p \rightarrow h_q hphq 两条路径变成了 h 0 → h p h_0 \rightarrow h_p h0hp h n → h q h_n \rightarrow h_q hnhq,代价相差: ( h 0 − h n ) 2 + ( h p − h q ) 2 − ( h 0 − h p ) 2 − ( h n − h q ) 2 (h_0-h_n)^2+(h_p-h_q)^2-(h_0-h_p)^2-(h_n-h_q)^2 (h0hn)2+(hphq)2(h0hp)2(hnhq)2 − 2 h 0 h n − 2 h p h q + 2 h 0 h p + 2 h n h q -2h_0h_n-2h_ph_q+2h_0h_p+2h_nh_q 2h0hn2hphq+2h0hp+2hnhq − 2 h 0 ( h n − h p ) − 2 h q ( h p − h n ) -2h_0(h_n-h_p)-2h_q(h_p-h_n) 2h0(hnhp)2hq(hphn) − 2 ( h 0 − h p ) ( h n − h p ) -2(h_0-h_p)(h_n-h_p) 2(h0hp)(hnhp)

因为 h 0 ≤ h p ≤ h n h_0\le h_p\le h_n h0hphn,所以 − 2 ( h 0 − h p ) ( h n − h p ) ≥ 0 -2(h_0-h_p)(h_n-h_p)\ge 0 2(h0hp)(hnhp)0,因此前者要更大,即原本 h 0 h_0 h0 跳到 h n h_n hn h 0 h_0 h0 跳到任意一个 h p h_p hp 都要更优。

  1. 如果 x = p x=p x=p ,也就是p后面没有后继节点,p就是终点。路径从 h 0 → h n h_0 \rightarrow h_n h0hn 变成了 h 0 → h p h_0 \rightarrow h_p h0hp,显然不优。

综上,因此 h 0 h_0 h0 一开始一定是要跳到 h n h_n hn,否则不优。同理,离开 h 0 h_0 h0 后,这个点相当于消失了,这时候的最小值变成了 h 1 h_1 h1 ,从 h n h_n hn 一定是要跳到最小值 h 1 h_1 h1 上的。类推,因此路径一定是先从地面跳到最高点,再跳到最低点,再跳到次高点,再跳回次低点… 即路径为 h 0 → h n → h 1 → h n − 1 → . . . → h ⌈ n 2 ⌉ h_0 \rightarrow h_n \rightarrow h_1 \rightarrow h_{n-1}\rightarrow ... \rightarrow h_{\left\lceil\frac n 2\right\rceil} h0hnh1hn1...h2n

实现的话左右各放一个指针(双端队列也可以),模拟一遍路径累加一下长度就行了。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=305;
typedef long long ll;

int n,a[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	sort(a+1,a+n+1);
	int i=0,j=n;
	ll ans=0;
	while(i<j){
		ans+=1ll*(a[j]-a[i])*(a[j]-a[i]);
		i++;
		ans+=1ll*(a[j]-a[i])*(a[j]-a[i]);
		j--;
	}
	cout<<ans;
	return 0;
}

E

洛谷原题 P1080 [NOIP2012 提高组] 国王游戏

思路:

这个很有难度,而且需要使用高精度乘法和除法,应该是最难的题,直接干掉一票人(昨天晚上为止就两个人做出来)。

考虑如果编号 i i i i + 1 i+1 i+1 的两个大臣交换位置,代价会怎么变:

首先 i i i 前面和 i + 1 i+1 i+1 后面的大臣得到的金币数量是没有变的,只有 i i i i + 1 i+1 i+1 两个人变了。如果要它们交换会影响最大值,那么这两个人中一定有一个为最大值,要不然换不换无所谓。假设前 x x x 个人左手上的乘积为 s x s_x sx ,那么:

  • 如果大臣 i i i 在前面,那么
    • 大臣 i i i 的金币为: s i − 1 / b i s_{i-1}/b_i si1/bi 1 ◯ \qquad \textcircled 1 1
    • 大臣 i + 1 i+1 i+1 的金币为: s i − 1 ∗ a i / b i + 1 s_{i-1}*a_i/b_{i+1} si1ai/bi+1 2 ◯ \qquad \textcircled 2 2
  • 如果大臣 i + 1 i+1 i+1 在前面,那么
    • 大臣 i i i 的金币为: s i − 1 ∗ a i + 1 / b i s_{i-1}*a_{i+1}/b_i si1ai+1/bi 3 ◯ \qquad \textcircled 3 3
    • 大臣 i + 1 i+1 i+1 的金币为: s i − 1 / b i + 1 s_{i-1}/b_{i+1} si1/bi+1 4 ◯ \qquad \textcircled 4 4

我们要找 i i i 在前和 i + 1 i+1 i+1 在前两种情况中 两人获得金币的最大值 较小的那个。假如大臣 i i i 在前面要较小( 大臣 i + 1 i+1 i+1 同理,因此只需要证明大臣 i i i 在前需要满足什么条件即可)。

  1. 我们把上面的四个式子标上序号,如果 1 ◯ \textcircled 1 1 式是最大值,那么应当满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 12。当大臣 i + 1 i+1 i+1 在前时, 1 ◯ → 3 ◯ \textcircled 1\rightarrow \textcircled 3 13,最大值显然变大,符合假设(也就是 1 ◯ \textcircled 1 1 是第一种情况的最大值,同时小于第二种情况的最大值,即两个最大值中较小的那个)。因此此时只需要满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 12 即可,即 b i + 1 ≥ a i ∗ b i b_{i+1}\ge a_i*b_i bi+1aibi

  2. 如果 2 ◯ \textcircled 2 2 式是最大值,那么应当满足 1 ◯ ≤ 2 ◯ \textcircled 1 \le \textcircled 2 12。当大臣 i + 1 i+1 i+1 在前时, 2 ◯ → 4 ◯ \textcircled 2\rightarrow \textcircled 4 24 会变小,要维持 2 ◯ \textcircled 2 2 是两种情况中较小的最大值,需要 3 ◯ \textcircled 3 3 是第二种情况的最大值,同时满足 2 ◯ ≤ 3 ◯ \textcircled 2 \le \textcircled 3 23 1 ◯ ≤ 2 ◯ ⇒ b i + 1 ≤ a i ∗ b i \textcircled 1 \le \textcircled 2 \Rightarrow b_{i+1}\le a_i*b_i 12bi+1aibi 2 ◯ ≤ 3 ◯ ⇒ a i ∗ b i ≤ a i + 1 ∗ b i + 1 \textcircled 2 \le \textcircled 3 \Rightarrow a_i*b_i\le a_{i+1}*b_{i+1} 23aibiai+1bi+1

仔细观察两个条件,发现两个条件可以合并成一个条件,即 a i ∗ b i ≤ a i + 1 ∗ b i + 1 a_i*b_i\le a_{i+1}*b_{i+1} aibiai+1bi+1这说明在满足上面这个式子时,大臣 i i i 在前时不管谁取得最大值,得到的最大值要更小。因此满足这个条件时,大臣 i i i 要放在前面。

之后两两比较,把 a i ∗ b i a_i*b_i aibi 更小的放在前面,可以通过冒泡排序把序列排序,按 a i ∗ b i a_i*b_i aibi 升序排列。排好序后,我们只需要从头枚举到尾,看每个人能得到多少金币,最大值即为所求,因为 n ≤ 1000 a i ≤ 10000 n\le1000 \quad a_i\le 10000 n1000ai10000 ,前缀积最多能有最多4000位,需要用高精度。

code:

高精乘高精,高精除低精。我个人建议除了除法,加减乘都只写高精运算高精,再写一个普通数转化高精数的函数就可以了。反正都用高精了,效率一般不是问题,这样写码量少很多。备好板子,赛时要用直接抄板子。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn=1005;

int n;
struct peo{
	int a,b;
	bool operator<(peo x){
		return a*b<x.a*x.b;
	}
}a[maxn];

vector<int> g(int x){
	vector<int> C;
	do{
		C.push_back(x%10);
		x/=10;
	}while(x);
	return C;
}
vector<int> mul(vector<int> A,vector<int> B){
	vector<int> C(A.size()+B.size());
	for(int i=0;i<A.size();i++){
		for(int j=0;j<B.size();j++){
			C[i+j]+=A[i]*B[j];
		}
	}
	for(int i=0;i<C.size();i++)
		if(C[i]>9){
			C[i+1]+=C[i]/10;
			C[i]%=10;
		}
	while(C.size()>1 && C.back()==0)
		C.pop_back();
	return C;
}
vector<int> div(vector<int> A,int b){
	vector<int> C(A.size());
	int r=0;
	for(int i=A.size()-1;i>=0;i--){
		r=r*10+A[i];
		C.push_back(r/b);
		r%=b;
	}
	reverse(C.begin(),C.end());
	while(C.size()>1 && C.back()==0)
		C.pop_back();
	return C;
}
vector<int> max(vector<int> A,vector<int> B){
	if(A.size()!=B.size())return (A.size()>B.size())?A:B;
	for(int i=A.size()-1;i>=0;i--)
		if(A[i]!=B[i])
			return (A[i]>B[i])?A:B;
	return A;
}
void print(vector<int> A){
	for(int i=A.size()-1;i>=0;i--)
		printf("%d",A[i]);
	puts("");
}

int main(){
	cin>>n;
	for(int i=0;i<=n;i++)
		cin>>a[i].a>>a[i].b;
	sort(a+1,a+n+1);
	
	vector<int> s(g(a[0].a)),ans(g(0));
	for(int i=1;i<=n;i++){
		ans=max(ans,div(s,a[i].b));
		s=mul(s,g(a[i].a));
	}
	print(ans);
	return 0;
}

F

洛谷原题 P1842 [USACO05NOV] 奶牛玩杂技

思路:

和上一个题思路一模一样,不过因为没有高精所以好写很多。

考虑如果编号 i i i i + 1 i+1 i+1 的两个奶牛交换位置,压扁指数会怎么变:

首先 i i i 上面和 i + 1 i+1 i+1 下面的奶牛压扁指数是没有变的,只有 i i i i + 1 i+1 i+1 两个奶牛变了。如果要它们交换会影响最大值,那么这两个奶牛中一定有一个为最大值,要不然换不换无所谓。假设上面 x x x 个奶牛重量之和为 s x s_x sx ,那么:

  • 如果奶牛 i i i 在上面,那么
    • 奶牛 i i i 的压扁指数为: s i − 1 − b i s_{i-1}-b_i si1bi 1 ◯ \qquad \textcircled 1 1
    • 奶牛 i + 1 i+1 i+1 的压扁指数为: s i − 1 + a i − b i + 1 s_{i-1}+a_i-b_{i+1} si1+aibi+1 2 ◯ \qquad \textcircled 2 2
  • 如果奶牛 i + 1 i+1 i+1 在上面,那么
    • 奶牛 i i i 的压扁指数为: s i − 1 + a i + 1 − b i s_{i-1}+a_{i+1}-b_i si1+ai+1bi 3 ◯ \qquad \textcircled 3 3
    • 奶牛 i + 1 i+1 i+1 的压扁指数为: s i − 1 − b i + 1 s_{i-1}-b_{i+1} si1bi+1 4 ◯ \qquad \textcircled 4 4

我们要找 i i i 在上和 i + 1 i+1 i+1 在上两种情况中 两奶牛压扁指数的最大值 较小的那个。假如奶牛 i i i 在上面要较小( 奶牛 i + 1 i+1 i+1 同理,因此只需要证明奶牛 i i i 在上面需要满足什么条件即可)。

  1. 我们把上面的四个式子标上序号,如果 1 ◯ \textcircled 1 1 式是最大值,那么应当满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 12。当奶牛 i + 1 i+1 i+1 在上时, 1 ◯ → 3 ◯ \textcircled 1\rightarrow \textcircled 3 13,最大值显然变大,符合假设(也就是 1 ◯ \textcircled 1 1 是第一种情况的最大值,同时小于第二种情况的最大值,即两个最大值中较小的那个)。因此此时只需要满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 12 即可,即 b i + 1 ≥ a i + b i b_{i+1}\ge a_i+b_i bi+1ai+bi

  2. 如果 2 ◯ \textcircled 2 2 式是最大值,那么应当满足 1 ◯ ≤ 2 ◯ \textcircled 1 \le \textcircled 2 12。当奶牛 i + 1 i+1 i+1 在上时, 2 ◯ → 4 ◯ \textcircled 2\rightarrow \textcircled 4 24 会变小,要维持 2 ◯ \textcircled 2 2 是两种情况中较小的最大值,需要 3 ◯ \textcircled 3 3 是第二种情况的最大值,同时满足 2 ◯ ≤ 3 ◯ \textcircled 2 \le \textcircled 3 23 1 ◯ ≤ 2 ◯ ⇒ b i + 1 ≤ a i + b i \textcircled 1 \le \textcircled 2 \Rightarrow b_{i+1}\le a_i+b_i 12bi+1ai+bi 2 ◯ ≤ 3 ◯ ⇒ a i + b i ≤ a i + 1 + b i + 1 \textcircled 2 \le \textcircled 3 \Rightarrow a_i+b_i\le a_{i+1}+b_{i+1} 23ai+biai+1+bi+1

仔细观察两个条件,发现两个条件可以合并成一个条件,即 a i + b i ≤ a i + 1 + b i + 1 a_i+b_i\le a_{i+1}+b_{i+1} ai+biai+1+bi+1这说明在满足上面这个式子时,奶牛 i i i 在上时不管谁取得最大值,得到的最大值要更小。因此满足这个条件时,奶牛 i i i 要放在上面。

之后两两比较,把 a i + b i a_i+b_i ai+bi 更小的放在上面,可以通过冒泡排序把序列排序,按 a i + b i a_i+b_i ai+bi 升序排列。排好序后,我们只需要从头枚举到尾,记录一下最大的压扁指数即可(压扁指数可以是负数)。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e4+5;

int n;
struct cow{
	int w,s;
	bool operator<(cow x){
		return w+s<x.w+x.s;
	}
}a[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i].w>>a[i].s;
	sort(a+1,a+n+1);
	int ans=-1145141919,t=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,t-a[i].s);
		t+=a[i].w;
	}
	cout<<ans;
	return 0;
}

G

洛谷原题 P1803 凌乱的yyy / 线段覆盖

思路:

这也是个典型的贪心,按比赛结束时间排序,每次枚举结束时间,尝试在这个结束时间之前塞入最多的比赛。

感性理解的话,如果你在某时刻会闲下来,之后选一个能选的比赛中结束时间最早的比赛来打,这样比赛结束后对后续选择影响也最小。
如果选择结束时间最早的比赛不优,那么肯定存在一场结束时间不是最早的比赛,打它会更优,但是选择前者可以打的比赛数量+1,同时闲下来的时间更早;选择后者打的比赛数量+1,但闲下来的时间要晚。显然不优,和前面的假设就矛盾了。

因此对比赛按结束时间排序,每次尝试打一场结束比赛最早的比赛,直到遍历一遍比赛,看看能打几场,就是答案。如果要看严谨证明的话可以去洛谷题解区看。

codeforces上有一期比赛有道题考了一道有点类似的,指路好吧可能不太一样

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e6+5;

int n;
struct data{
	int s,e;
	bool operator<(data x){
		if(e!=x.e)return e<x.e;
		return s<x.s;
	}
}a[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i].s>>a[i].e;
	sort(a+1,a+n+1);
	
	int ans=0,lst=0;
	for(int i=1;i<=n;i++){
		if(a[i].s>=lst){
			ans++;
			lst=a[i].e;
		}
	}
	cout<<ans;
	return 0;
}

H

洛谷原题 P1589 泥泞路

思路:

题目也没说是开区间还是闭区间,看样例推测应该是半开半闭,这样就不需要考虑端点了。

我们只铺一段区间的时候,肯定从左端点开铺,铺到大于等于右端点就行了。但是这时有可能盖到下一段区间的一部分,这时候肯定接着刚刚的继续铺,而不是重新从区间左端点开铺,有便宜为什么不占呢。

code:

一开始的想法:记录一下上一段铺的区间,如果上一段区间盖到了这段区间的一部分,就把这个区间合并到上一个区间上,铺的时候一块铺。如果没盖到,把上一个区间铺了,改为记录这个区间。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;

int n,L;
struct mud{
	int s,e;
	bool operator<(mud x){
		return s<x.s;
	}
}a[maxn],b[maxn];
int cnt;

int main(){
	cin>>n>>L;
	for(int i=1;i<=n;i++)
		cin>>a[i].s>>a[i].e;
	sort(a+1,a+n+1);
	
	ll l=a[1].s,r=a[1].e,ans=0;
	for(int i=2;i<=n;i++){
		if(l+(r-l+L-1)/L*L>a[i].s){//合并区间 
			r=a[i].e;
		}
		else {
			ans+=(r-l+L-1)/L;
			l=a[i].s;
			r=a[i].e;
		}
	}
	ans+=(r-l+L-1)/L;
	cout<<ans;
	return 0;
}

简单点的想法,只记录上一次铺到的位置。如果铺到的位置大于当前区间左端点,接着刚才的位置继续铺。如果铺到的位置小于当前区间左端点,从区间左端点开始铺。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e4+5;

int n,L;
struct merge{
	int s,e;
	bool operator<(merge x){
		return s<x.s;
	}
}a[maxn],b[maxn];
int cnt;

int main(){
	cin>>n>>L;
	for(int i=1;i<=n;i++)
		cin>>a[i].s>>a[i].e;
	sort(a+1,a+n+1);
	
	long long ans=0;
	for(int i=1,r=0,cnt;i<=n;i++){//r表示上次铺到了哪里 
		r=max(r,a[i].s);
		cnt=max((a[i].e-r+L-1)/L,0);
		r=r+cnt*L;
		ans+=cnt;
	}
	cout<<ans;
	return 0;
}

I

洛谷原题 P1843 奶牛晒衣服

思路:

第一眼感觉是二分答案,然后才想到贪心。

二分答案的做法非常明显,就是二分用的时间,然后计算每件衣服需要烘干几次,如果总次数超过了时间,说明时间不够多。如果没有超过,说明时间够多了。

贪心的算法其实也很好理解,衣服晾干是同时进行的,所以最湿的衣服晾干了,其他衣服也就都干了,那么我们每次烘一次最湿的衣服,烘干次数等于晾的时间,如果最湿的衣服剩下的湿度小于晾的时间乘a。那么这个时间就是答案。因为要动态找最湿的衣服,所以用堆来存储。

code:

二分答案

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e5+5;

int n,a,b;
int w[maxn];

bool check(int t){
	int cnt=0;//烘干几次
	for(int i=1;i<=n;i++)
		if(1ll*a*t<w[i])
			cnt+=(w[i]-a*t+b-1)/b;
	return cnt<=t;
}

int main(){
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)
		cin>>w[i];
	
	int l=1,r=5e5,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}

堆+贪心

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn=5e5+5;

int n,a,b;
priority_queue<int> h;

int main(){
	cin>>n>>a>>b;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		h.push(t);
	}
	
	int cnt=0;
	while((h.top()+a-1)/a>=cnt){
		cnt++;
		int t=h.top()-b;
		h.pop();
		h.push(t);
	}
	cout<<cnt-1;
	return 0;
}

J

2023 清华大学学生程序设计竞赛暨高校邀请赛(THUPC2023)初赛

思路:

这个题好。很难。

贪心的想法,肯定让最大的数尽量当众数,最大的数有 a n a_n an 个,那么第 i i i 个数要出 m i n ( a i , a n ) min(a_i,a_n) min(ai,an) 个,每出一个,产生一次贡献 n n n。同理,如果最大数不够了,当不了众数了,在剩余的数中选取最大的那个继续当众数,以此类推。

那么对一个数 i i i ,它的前 a n a_n an 个产生了 n 个贡献,然后假如剩下的次大数为 k k k,则有 a k − a n a_{k}-a_{n} akan 个产生了 k k k 个贡献( a k > a n a_{k}>a_{n} ak>an),类推。1 ~ a n a_n an a n a_n an ~ a k a_k ak … 是固定的,对一个 a i a_i ai 我们只需要从小到大累加一遍即可,不够了后面就可以停了。所以对每个区间,需要记录一下右端点 r r r,这个值 k k k 是什么 i d id id,为了快速求和,在记录一下前缀和贡献 s s s。之后二分查找它最终所在的区间,累加一下第 i i i 个数的贡献即可。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;

int n,a[maxn];
ll r[maxn],id[maxn],s[maxn];
int cnt; 

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	
	ll ans=0;
	for(int i=n;i>=1;i--){
		if(a[i]>r[cnt]){
			r[++cnt]=a[i];
			id[cnt]=i;
			s[cnt]=s[cnt-1]+1ll*(r[cnt]-r[cnt-1])*i;
		}
		int idx=upper_bound(r+1,r+cnt+1,a[i])-r-1;
		ans+=s[idx]+1ll*(a[i]-r[idx])*id[idx+1];
	}
	
	cout<<ans;
	return 0;
}
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值