贪心算法主要思想:
解决最优方案问题时,每一次只找当前情况下的局部最优解。
P1007 独木桥
过桥时间最快和最慢分别对应贪心算法中所谓最好的情况,以及最坏的情况。
因为走完桥的总时间决定于最后走完的那个士兵
我们就以一个士兵为着眼点,看什么时候一个士兵可以最快走完,有事什么原因在相同微店之下是他速度减慢,最后在两种情况中分别考虑最慢的那种极端情况即可。
最快情况:
首先我们需要明确
在速度相同下 士兵是不可能碰上的
一个士兵没有折返走路(因为和另外一个士兵面对面碰上而这番),这时候他走得路程就是他的点离桥两端较近的那个
如果士兵选择了较远的那条路 而且和别的士兵碰上了导致折返,路程会增长。
用这两个结论:
我们构建所有士兵过桥最短和最长时间的模型
最短时间:
不会有士兵碰上:最靠中间的士兵走最近那一边的时间就是总时长 (简化 也就是在每个标点离短点两个距离中条最小值 这些最小值再进行比较选出最大)max(min(a[i],l+1-a[i]));
最长时间:
以折返的角度看,事情会变得非常不好想,于是我去翻题解,看到里面一个大佬想到的优雅好多的思考方式——灵魂对换。两个人折返不需要时间(即使需要时间也可以想程玲很兑换的问题 就是记个数最后一+就可了),那么就意味着可完全想成两个人再碰到以后穿透对方继续往前走知道撤离。那么最长的也就是离短点最近的那个往距离打的那方向走所用时间
max(a[n],l-a[1]+1);
源代码:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int l,n;
int a[5010];
int s1=0;
int s2=0;
int main(){
cin>>l>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(n==0){
cout<<"0 0";
return 0;
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
s1=max(min(a[i],(l+1-a[i])),s1);
}
s2 = max(a[n],l+1-a[1]);
cout<<s1<<' ';//输出最小的那个时间 速度都是1
cout<<s2;
return 0;
}
P1223 排队接水:
等待接水的平均时长是所有人等待的时长除以总人数,所有人等待的市场越少,平均市场越少。
所有人等待的总时长又是每个人等前面人接水的时长加上自己的时长。
粗略地想一下,后面的每个人都要等待前面某个人接水,假如那个人自己的接水时间很长的haul,后面的接水时间总计都要加上这个人,总时长偏大。
实际上这道题的逻辑有些像哈夫曼树,权值是接水时间,其后面的人数,是分支数,按照哈夫曼的逻辑,接水平均时长也是可以算出来一个加权路径长度的,那即是说,可以用哈夫曼树构建方法构建我们这个问题的加权路径长度(接水总时间)之中最优解
实际上两个问题用到的思想本质都一样,贪心。
永远在当前情况下,挑选最优的两个值(最小的两个权值,最短的接水时间),最优解排序,末尾的乘路径较少,越优越多,强弱均衡最后会得到最小值。
这样这道题的实质实际上就是一个排序优化算法。
快速排序如下:
快拍有几点要注意的:
1 while(a[i]<a[mid])错误 因为如果想这道题案例里面 第一斌i就扫到了mid的位置并a[i]a[j]交换元素,导致a[mid]是一个变量,最后无法保证ij碰上的时候,一面是小于某个值的另一边是大于某个值的,这个值本身都不统一 所以一定先把a[mid]在循环之前就付给一个变量 然后用a[i]a[j]拿他作比较
2 分界点元素不可以同时在左右递归函数里面 会出现 一个元素多个位置的bug 所以一定一个写j 一个写j+1
3 注意这道题 相同ti因为对应不同的原序标号,所以它被输出的顺序在这里是敏感的
比方说 假设我们的输入值是一堆1 那么使用不同的排序算法 b[]是不同的数组
为了统一答案 相同的数所对应b[i]按照从小到大输出
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <iomanip>
using namespace std;
int n;
double ans=0;
int a[1010],b[1010];
void quick(int a[],int b[], int l, int r){ //要注意 当出现重复的数是 无法固定顺序输出 比如说有一堆1那么输出的顺序可以五花八门 归并一个顺序 快爬一个顺序 冒泡一个顺序等等等
if(l>=r)return;
int mid = (l+r)/2;
int x=a[mid];
int i=l-1;
int j=r+1;
while(i<j){
do i++; while (a[i]<x);
do j--; while (a[j]>x);
if(i<j){
swap(a[i],a[j]);
swap(b[i],b[j]);
}
}
quick(a,b,l,j);
quick(a,b,j+1,r); //为什么我把i改成j就可以了?
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=i;
}
quick(a,b,1,n);
for(int i = 2; i<=n; i++){
if(a[i-1]==a[i] && b[i-1]>b[i]) swap(b[i-1],b[i]); //要注意 当出现重复的数是 无法固定顺序输出 比如说有一堆1那么输出的顺序可以五花八门 归并一个顺序 快爬一个顺序 冒泡一个顺序等等等解决重复ti的奇艺问题
printf("%d ",b[i-1]);
}
printf("%d", b[n]);
printf("\n");
for(int i=n;i>0;i--){
ans+=(i-1)*a[n-i+1];
}
cout<<fixed<<setprecision(2)<<ans/n*1.0;
return 0;
}
P1803 线段覆盖:
先用贪心想,后反证法证明正确。
如果想要打的比赛最多,就需要在每一个情况之下选择最早结束的,留给后面更多选择的余地。
证明:假设有一个可参加最多比赛的时间管理方式,但是存在至少一个某种情况下最早结束的比赛。
证明这种存在是不可能的 我们把前面已经选好的忽略掉,直接从这个不是最早开始的比赛开始考虑
那么 反: 这种情况下的最优时间安排中,第一场比赛全都不是最早结束的
也就是说存在更早结束的第一场比赛,那选择更早结束的第一场比赛去替换,显然后续能安排的比赛总数要大于等于上述描述(反中的)
得证
所以我们要做的:
我们需要读入一个代表开始时间的数组 也要读入一个代表结束时间的数组 并且开始结束时间要有某种联结关系
所以用结构体数组存储
排序 将结构体按照结束时间排序
最小的那个设为x 并且计数器++ 用指针指向第一个后面表示开始时间的元素 以第一个开始时间大于x的算 计数器++ 如此往复 知道i到了结尾n
源代码:
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int ans=1;
int x;
struct t{ //分别记录开始时间和结束时间
int s;
int c;
}a[1000010];
bool cmp(t a, t b){
return a.c<b.c;
} //paixu
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].s,&a[i].c);
}
//给结束时间陪个续
sort(a+1,a+n+1,cmp);
x=a[1].c;
int i=1;
while(i<=n){
i++;
if(a[i].s>=x){
ans++;
x=a[i].c;
}
}
printf("%d",ans);
return 0;
}
P1031 均摊问题:
以某一个相邻的牌为单位单独去想,如果里面不上来就是平均数的话,至少要平分一次,所以,我们就把它控制在一次级以内。让其中一个为平均值,并且不再处于中间而是处于边缘,这样我们在下一次的时候就可以忽略它了。
第一个小于平均值 第二个要给第一个补上
第一个大于平均值就补给第二个
第一个等于了i++ 就此不管了
源代码:
#include <iostream>
using namespace std;
int n, sum=0;
int a[110];
int ans=0;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
int N=sum/n;
for(int i=1;i<=n;i++){
if(a[i]!=N){
a[i+1]+=a[i]-N; //判断是否等于0 //a[i]不用真的加上 它的哪个值已经不影响判断了 因为反正再给a[i+1]做这个操作同时 a[i]已经补完了
ans++;
}
}
printf("%d",ans);
return 0;
}