赞
踩
在每个决策点作出在当时看来最佳的选择,即总是遵循某种规则,做出局部最优的选择,以推导出全局最优解(局部最优解->全局最优解)
贪心算法解决的正确性虽然很多时候看起来是显而易见的,但是要严谨地证明算法能够得到最优解,并不是件容易的事。所以,很多时候,我们只需要多举几个例子,看一下贪心算法的解决方案是否真的能得到最优解就可以了。
简单的题多用排序
几道例题
https://www.luogu.com.cn/problem/P2945
https://www.luogu.com.cn/problem/P5602
每个人的花费时间=每个人装满水桶的时间+等待时间。
显然越小的排在越前面(先接水)得出的结果越小
#include<stdio.h> #include<iostream> #include<algorithm> using namespace std; int main() { int a[1000],b[0100],n,r; scanf("%d%d",&n,&r); int i,sum=0; int temp; for(i=0; i<n; i++) scanf("%d",&a[i]); sort(a,a+n); //r个水龙头,分队 for(i=0; i<r; i++) { temp = 0; //计算每队时间,越小的越先接水 for(int j=i;j<n;j = j + r) { temp += a[j]; sum += temp; } } printf("%d\n",sum); }
https://www.luogu.com.cn/problem/P1223
涉及到了多属性排序:重写Arrays.sort()方法
import java.io.*; import java.util.*; //外部类 class Person { int index; int time; } public class Main { public static void main(String[] args) throws IOException { StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken(); int n = (int)in.nval; Person[] people = new Person[n]; for (int i = 0; i < n; i++) { //记得创建对象! people[i] = new Person(); people[i].index = i + 1; in.nextToken(); people[i].time = (int)in.nval; } //重写,这里是重小到大 Arrays.sort(people, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if(o1.time == o2.time) return o1.index - o2.index; else return o1.time - o2.time; } }); long sum = 0; long t = 0; for(int i = 0;i < n;i++) { out.print(people[i].index); if(i != n - 1) { out.print(" "); } sum += t; t += people[i].time; } out.println(); out.printf("%.2f",(sum * 1.0)/n); out.close(); } }
1.如果你想到把每堆牌的张数减去平均张数,题目就变成移动正数,加到负数中,使大家都变成0
2.正数可移,故负数也可移
#include <stdio.h> int n; int a[105]; int main() { int i,j; int aver = 0,step=0; //aver记录每堆纸牌的平均值 step记录移动次数 scanf("%d",&n); for(i=0;i<n;i++) { scanf("%d",&a[i]); aver+=a[i]; } aver/=n; for(i=0;i<n;i++) a[i]-=aver; i=0,j=n-1; while(i<j) { if(a[i] != 0){ a[i+1]+=a[i]; //将第i堆纸牌移到第i+1堆上 a[i]=0; //第i堆牌移走后变为0 step++; } i++; } printf("%d\n",step); return 0; }
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int N,R; int ans=0; //标记数 int point[1010]; int main() { int i,s,p; scanf("%d",&N); scanf("%d",&R); for(i=0;i<N;i++) scanf("%d",&point[i]); sort(point,point+N);//预先对点按位置排序 i=0; while(i<N) { s=point[i]; //s是没有被覆盖的最左的点的位置 while(i<N && point[i]<=s+R) //一直向右前进直到第一个距s的距离大于R的点 i++; p=point[i-1]; //p是新加上标记的点的位置 while(i<N && point[i]<=p+R) //一直向右前进直到第一个距p的距离大于R的点,方便继续循环 i++; ans++; } printf("%d\n",ans); return 0; }
https://www.luogu.com.cn/problem/P5541
有n个物体,第i个物体的重量为wi(wi为正整数)。选择尽量多的物体,使得总重量不超过C。
**优先取重量小的物体。**对n个物体按重量递增排序,依次选择每个物体直到装不下为止。
有时考虑条件可能需要转换后得出https://www.luogu.com.cn/problem/P2616
有n个物体,第i个物体的重量为wi,价值为vi(wi, vi均为正整数)。在总重量不超过C的情况下让总价值尽量高。每一个物体都可以只取走一部分,价值和重量按比例计算。
优先取性价比高的物体。
又称【区间调度问题】
(1)构造区间
(2)右递增
(3)遍历取左侧大于等于当前最右侧的区间
参与尽可能多的工作,如果选择了参与,那么自始至终都必须全程参与。此外,参与工作的时间段不能重叠
方法:在可选的工作中,每次都选取结束时间最早的工作(b递增)
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int maxn=100005; int N; int ans=0; struct Work //定义工作 { int s; //开始时间 int t; //结束时间 } w[maxn]; bool cmp(struct Work w1,struct Work w2) //按工作结束时间递增排序 { return (w1.t<w2.t); } int main() { int i,j; int end; //end记录最后所选活动的结束时间 scanf("%d",&N); for(i=0;i<N;i++) scanf("%d %d",&w[i].s,&w[i].t); sort(w,w+N,cmp); j=ans=0; end=0; for(j=0;j<N;j++) { if(w[j].s>end) //若发现某项工作的开始时间比end晚 { ans++; //则选择当前工作 end=w[j].t; //将这项工作的结束时间作为end } } printf("%d\n",ans); return 0; }
https://www.cnblogs.com/acgoto/p/9824723.html
(1)构造区间
(1)先右递增,再左递减
(2)取未覆盖的第一个区间的最右点。
如下图,如果选灰色点,移动到黑色点更优。
#include <iostream> #include <cstdio> #include <algorithm> #define maxn 10010 using namespace std; int n; int ans; //最少区间数量 struct Seg //定义区间 { int a; int b; } s[maxn]; bool cmp(struct Seg s1,struct Seg s2) //自定义排序规则 { if(s1.b!=s2.b) return (s1.b<s2.b); else return (s1.a>s2.a); } int main() { int i,loc; //loc记录当前选择的区间终点 scanf("%d",&n); for(i=0;i<n;i++) scanf("%d %d",&s[i].a,&s[i].b); sort(s,s+n,cmp); ans=1; //排序完成后,选一个点,位置在第一个区间的终点 loc=s[0].b; for(i=1;i<n;i++) //遍历后续区间 { if(s[i].a>loc) //若发现后面有区间的起点比loc大 { ans++; //则应再选一个点 loc=s[i].b; //并修改loc为新区间的终点 } } printf("%d\n",ans); return 0; }
给定一个长度为m的区间**(连续的)**,再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖。(给定一条指定的线段[s,t],数轴上有n个闭区间[ai,bi],求最少使用多少区间可以覆盖整条线段。)
(1)构造区间
(2)先左递增,再右递增
(2)设置一个变量表示已覆盖到的区间右端点,在剩下的线段中找出所有左端点小于等于当前已覆盖到的区间右端点的线段(使区间连续),选择右端点最大并且大于当前已覆盖到的区间右端点(否则题目无解),重复以上操作直至覆盖整个区间;
https://www.cnblogs.com/acgoto/p/9824723.html
#include<bits/stdc++.h> using namespace std; const int maxn=10005; int t,n,k,cnt,pos,beg;double w,h,lb,xi,ri,maxv;pair<double,double> itv[maxn];bool flag; int main(){ while(cin>>t){ while(t--){ cin>>n>>w>>h;h/=2.0;pos=cnt=0;//h取一半 //构造得到“n条线段的起点和终点”!!! for(int i=0;i<n;++i){ cin>>xi>>ri; //ri<h的圆不可能做到全覆盖,舍去! if(ri<h)continue; //计算该圆能完全覆盖的部分! itv[pos].first=xi-sqrt(ri*ri-h*h); itv[pos++].second=xi+sqrt(ri*ri-h*h); } //(1)排序 sort(itv,itv+pos);lb=0;beg=0;flag=false; //最左端点不能从起始开始,无方案 if(itv[0].first>0){cout<<0<<endl;continue;} //有方案 while(lb<w){ maxv=0; //itv[k].first<=lb这样保证整个区间是连续的,即草坪都会被润湿 for(k=beg;k<pos&&itv[k].first<=lb;++k) //(2)找线段左端点在lb以内右端点能覆盖到的最远距离 maxv=max(maxv,itv[k].second); //能到达的最右端大于当前已覆盖到的区间右端点 if(maxv>lb)cnt++,lb=maxv;//beg=k;不用吧 //否则说明不能覆盖整个区间,直接退出,输出0 else {flag=true;break;} } if(flag)cout<<0<<endl; else cout<<cnt<<endl; } } return 0; }
每次可选一段区间进行操作
https://www.luogu.com.cn/problem/P5019
类似的还有https://www.luogu.com.cn/problem/P1969 https://www.luogu.com.cn/problem/P3078
import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); int result = 0; in.nextToken(); int n = (int)in.nval; int[] s = new int[n]; for(int i = 0;i < n;i++) { in.nextToken(); s[i] = (int)in.nval; } for(int i = 1;i < n;i++) { if(s[i] > s[i - 1]) { result += s[i] - s[i - 1]; } } out.print(result + s[0]); out.close(); } }
区间覆盖(所要覆盖的大区间是分散的……)
import java.io.*; import java.util.*; class Node{ int a; int b; } public class Main { public static void main(String[] args) throws IOException { StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken(); int n = (int)in.nval; in.nextToken(); int l = (int)in.nval; Node[] s = new Node[n]; for(int i = 0;i < n;i++) { s[i] = new Node(); in.nextToken(); s[i].a = (int)in.nval; in.nextToken(); s[i].b = (int)in.nval; } Arrays.sort(s,new Comparator<Node>() { @Override public int compare(Node n1, Node n2) { // TODO Auto-generated method stub return n1.a - n2.a; } }); int start = s[0].a,result = 0; for(int i = 0;i < n;i++) { start = Math.max(start,s[i].a); while(start < s[i].b) { start += l; result++; } } out.print(result); out.close(); } }
每段距离小于dist的中间点去掉
import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken(); int n = (int)in.nval; in.nextToken(); int dist = (int)in.nval; int[] s = new int[n]; for(int i = 0;i < n;i++) { in.nextToken(); s[i] = (int)in.nval; } Arrays.sort(s); int result = 0; //对中间的点进行判断,看是否用除去 for(int i = 1;i < n - 1;i++) { if(s[i + 1] - s[i - 1] <= dist) { //除去该点,并使前面的点后移以便后续计算 s[i] = s[i - 1]; result++; } } out.print(result); out.close(); } }
切分段数,使距离最小:去掉相距最远的几个点距离
import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); in.nextToken(); int n = (int)in.nval; in.nextToken(); int m = (int)in.nval; int[] s = new int[n]; int[] j = new int[n - 1]; for(int i = 0;i < n;i++) { in.nextToken(); s[i] = (int)in.nval; } for(int i = 1;i < n;i++) { j[i - 1] = s[i] - s[i - 1]; } Arrays.sort(j); //每段距离均要加一,m段则要加上m个1 int result = m; for(int i = 0;i < n - m ;i++) { result += j[i]; } out.print(result); out.close(); } }
问题:设有n个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数。
方法:按字典序从大到小输出即可
(字符串排序的规则:首先按字典序,然后看串长度。如7>414321>4143>32)
<https://blog.csdn.net/ds19980228/article/details/82714478?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
#include <iostream> #include <algorithm> using namespace std; string num[25]; //最多25个数(字符串) bool cmp(string a,string b) //字典序从大到小排序 { return (a+b > b+a); } int main() { int i,n; cin>>n; for(i=0;i<n;i++) cin>>num[i]; sort(num,num+n,cmp); for(i=0;i<n;i++) cout<<num[i]; //直接输出即可 cout<<endl; return 0; }
问题:一个高精度的正整数n(≤240位),去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数
方法:
#include <stdio.h> #include <string.h> #define maxlen 245 char n[maxlen]; int s; int main() { int i,j,k; int flag,len; //flag记录删除的数是否是最后一位 0-是 1-不是 scanf("%s",n); scanf("%d",&s); len=strlen(n); for(i=0;i<s;i++) { //标示是否找到递减区间 flag=0; for(j=0;j<len-1;j++) { if(n[j]>n[j+1]) //找到递减区间的第一个元素 { for(k=j;k<len-1;k++) //"删除" n[k]=n[k+1]; len--; //同时修改数的长度 flag=1; break; } } if(flag==0) //没有找到递减区间,则去掉最后一个数 len--; } for(i=0;i<len;i++) printf("%c",n[i]); printf("\n"); return 0; }
问题:构造**字典序尽可能小(小的在前)**的字符串T
方法:
https://blog.csdn.net/weixin_41676901/article/details/80615415
#include<iostream> using namespace std; #define MAX 2000 char S[MAX+1]; int main () { int n; cin >> n; for (int i = 0; i < n; i++) cin >> S[i]; int a = 0, b = n - 1; while (a <= b) { bool vis = false; //针对开头和尾部字符相同的情况,我们希望能够尽早使用更小的字符!! for (int i = 0; i + a <= b; i++) { //从字符串的头和尾作比较 if (S[a + i] < S[b - i]) { vis = true; break; } if (S[a + i] > S[b - i]) { vis = false; break; } } if (vis) cout << S[a++]; else cout << S[b--]; } cout << endl; return 0; }
https://blog.csdn.net/every__day/article/details/87252852
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wyLziFXk-1595651515299)(C:\Users\ddjj6\AppData\Roaming\Typora\typora-user-images\1582469479796.png)]
我们每个字符看作一个节点,并且附带把频率放到**优先级队列(频率从小到大,左边出队,取两最小的)**中。我们从队列中取出频率最小的两个节点A、B,然后新那一个节点C,把频率设置为两个节点的频率之和,并把这个新节点C作为节点A、B的父节点。最后两把C节点放到优先级队列中。重复这个过程,直到队列中只剩一个数据(这个结果往往是要求的最小解)。
逻辑上:哈夫曼树 实际操作:优先队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gwPkE6j-1595651515307)(C:\Users\ddjj6\AppData\Roaming\Typora\typora-user-images\1582469551030.png)]
现在,给每一条边画一个权值,指向左子节点的边我们统统标记为0,指向右子节点的过,我们统统标记为1,那从根节点到叶子节点的路径就是叶子节点对应字符的霍夫曼编码。
这里每次取两最大的处理后再放回去,最后队列剩下的最后元素就是最小的
https://blog.csdn.net/jianglw1/article/details/83037103
#include <stdio.h> #include <iostream> #include <cstdlib> #include <cmath> #include <cctype> #include <string> #include <cstring> #include <algorithm> #include <stack> #include <queue> #include <set> #include <map> #include <ctime> #include <vector> #include <fstream> #include <list> #include <iomanip> #include <numeric> using namespace std; typedef long long ll; typedef unsigned long long ull; priority_queue<double,vector<double>,less<double> >w; //优先队列 int main() { int n; double tmp; cin>>n; //输入并入队 while(n--){ scanf("%lf",&tmp); w.push(tmp); } double a,b; //结点合并并入队,最后队中剩下的为结果 while(w.size()>1){ a=w.top(); w.pop(); b=w.top(); w.pop(); //新结点入队 w.push(2.0*sqrt(a*b)); } //cout.precision(3);// //cout<<w.top()<<endl;// printf("%.3f\n",w.top());//输出方式 return 0; }
森林?
#include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> #include <queue> using namespace std; const int maxn = 100000+23; priority_queue<int,vector<int>,greater<int> > q; int main() { int T; scanf("%d",&T); while(T--){ int n,m,k; scanf("%d%d%d",&n,&m,&k); //输入并入队 for(int i=1;i<=n;i++){ int x;scanf("%d",&x); q.push(x); } //结点合并并入队 while(n>m){ q.pop(); int a=q.top(); q.pop(); q.push(a+k); n--; } while(q.size()!=1) q.pop(); printf("%d\n",q.top()); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。