贪心
贪心算法将问题分解为一个个的子问题,求每个子问题的解,然后合起来得最优解。
贪心必须对每个子问题的解都做当前最好的选择,而不关心未来的选择;动态规划则会根据之前的结果对当前进行选择。
题目小结
纯粹的贪心思想
[ABC103D] Islands War
想要拆除的次数的最少,也就意味着每次拆除阻止的战争都要最多,可以将每场战争按bi升序排列,每次拆除bi与bi-1之间的桥,这样bi之前与bi之后之间就不会发生战争,能阻止的战争也最多,接下来就转化为拆除bi之后的桥阻止战争的子问题。
package 贪心;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class ABC103D_Islands_War {
static class war{
int a,b;
}
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int n,m,f,ans=0;
war[] war =new war[100005];
n=in.nextInt();m=in.nextInt();
for(int i=1; i<=m; i++){war[i]=new war();war[i].a=in.nextInt();war[i].b=in.nextInt();}
Arrays.sort(war, 1, 1 + m, new Comparator<ABC103D_Islands_War.war>() {
@Override
public int compare(ABC103D_Islands_War.war o1, ABC103D_Islands_War.war o2) {
if (o1.b!=o2.b)return o1.b-o2.b;
else return o1.a-o2.a;
}
});
f=war[1].b;ans=1;
for(int i=2; i<=m; i++){
if (war[i].a>=f){ans++;f=war[i].b;}
}
System.out.println(ans);
}
}
P1007 独木桥
对于最小值,肯定要所有士兵同向行走,那就算出每个士兵往左端和右端的最小值,从中选出最大值。
对于最大值,肯定要从中间分开,两边对着行走,可以看成每次碰撞无影响。算出第一个士兵到最右侧和最后一个士兵到最左侧的较大值。
package 贪心;
import java.util.*;
public class p1007独木桥 {
public static void main(String[] args) {
//灵魂互换
int l,n;
Integer[] a=new Integer[5005];
Scanner in=new Scanner(System.in);
l=in.nextInt();n=in.nextInt();
if (n==0) {System.out.println(0+" "+0);return;}
for(int i=1; i<=n; i++)
a[i]=in.nextInt();
Arrays.sort(a,1,n+1);
for(int i=1; i<=n; i++){
System.out.println(a[i]);
}
int t,min=0;
for(int i=1; i<=n; i++){
t=Math.min(a[i],l+1-a[i]);
min=Math.max(min,t);
}
System.out.println(min+" "+Math.max(l+1-a[1],a[n]));
}
}
P1031 [NOIP2002 提高组] 均分纸牌
这个的贪心思想是不满足题意的牌堆由它最近的牌堆分牌
package 贪心;
import java.util.Scanner;
public class P1031均分纸牌 {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int n,a[]=new int[105],t,sum=0,avg,ans=0,f=0,j=0;
n=in.nextInt();
for(int i=1; i<=n; i++){
a[i]=in.nextInt();
sum+=a[i];
}
avg=sum/n;
for(int i=1; i<=n; i++){
a[i]=a[i]-avg;
}
for(int i=1; i<=n; i++){
if (f==0){f=a[i];j=i;}//初始化
else {
t=f;
f+=a[i];
if (t*f<=0){ans+=Math.abs(i-j);j=i;}//贪心思想的体现,t*f<=0时,一次分完所有牌堆这是次数最小
}
}
System.out.println(ans);
}
}
排座椅
因为每一次划线对一名同学最多隔离一个同学,所以直接找出说话最多的每行每列画线即可。但以后需要仔细读题,输出要求是行号列号从小到大。
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
struct s{
int f,n;
}r[2005],c[2005];
bool cmp(const s a, const s b){
return a.n>b.n;
}
int main()
{
int m,n,k,l,d,r1,r2,c1,c2;
int a[2005];
cin>>m>>n>>k>>l>>d;
for(int i=1; i<=m; i++)
r[i].f=i;
for(int i=1; i<=n; i++)
c[i].f=i;
for(int i=1; i<=d; i++){
cin>>r1>>c1>>r2>>c2;
if(r1==r2){c[c1>c2?c2:c1].n++;}
else if(c1==c2){r[r1>r2?r2:r1].n++;}
}
sort(r+1,r+1+m,cmp);
sort(c+1,c+1+n,cmp);
//输出
for(int i=1; i<=k; i++)
a[i]=r[i].f;
sort(a+1,a+1+k);
for(int i=1; i<=k; i++)
cout<<a[i]<<" ";
cout<<endl;
for(int i=1; i<=l; i++)
a[i]=c[i].f;
sort(a+1,a+1+l);
for(int i=1; i<=l; i++)
cout<<a[i]<<" ";
return 0;
}
守望者的逃离
这个题做的我要死了,用贪心好多情况需要考虑,太费脑子了。
分析:
判断使用闪烁优还是跑步优,如果不能逃出距离更远好,如果能逃出时间更短好。
先用魔力完成闪烁,保证时间够且距离<s。
然后魔力可分为3种情况:
1、0-1,需要4秒才能完成闪烁60m,跑步能68m
2、2-5,需要3秒才能完成闪烁60m,跑步能跑51m
3、6-9,需要2秒才能完成闪烁60m,跑步能跑34m
第一种情况闪烁完后成为第二种情况。完成两次跳跃7秒,120m,跑步119m。
每次算出跑步需要的时间,进行一次闪烁需要的时间
if 最后几秒跑步能跑到终点 && 时间少于闪烁,那就跑步。
else if 第2、3情况 && 时间足够,闪烁
else if 第1种情况 && 时间足够 && 少于跑步到终点的时间,闪烁。
int main()
{
int m,s,t,maxx=0,t1;
cin>>m>>s>>t;
t1=t;
while(t1>0 && m/10 && maxx<s){
maxx+=60;
t1--;
m-=10;
}
int t2,t3;
while(t1 && maxx<s){
t2=ceil((double)(10-m)/4)+1;
t3=ceil((double)(s-maxx)/17);
if(t3 < t2){maxx+=t3*17; t1=t1-t3;}
else if(t2<=3 && t2<=t1){maxx+=60; m=m+(t2-1)*4-10; t1-=t2;}
else if(t2==4 && t1>=7 && t3>=7){maxx+=120; m=m+5*4-20; t1-=7;}
else {maxx+=17; t1--;}
}
if(maxx>=s)cout<<"Yes"<<endl<<t-t1;
else cout<<"No"<<endl<<maxx;
return 0;
}
贪心背包
混合牛奶
就是一个贪心背包问题,先买奶便宜量大的,排序即可
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
void solve(){
}
struct milk{
int p,sn;
};
bool cmp(const milk a, const milk b){
if(a.p<b.p)return true;
else if(a.p==b.p)return a.sn-b.sn;
else return false;
}
milk mi[2000005];
//全局变量再访问时越界也不会报错,会访问超出的内存空间
int main()
{
long long sum=0;
int m,n,i,num=0;
cin>>m>>n;
for(i=1;i<=n;i++)cin>>mi[i].p>>mi[i].sn;
//if(m==0 ){cout<<0;return 0;}
sort(mi+1,mi+1+n,cmp);
i=1;
/*for(i=1;i<=10;i++)cout<<mi[i].p<<" "<<mi[i].sn<<" ";*/
//这里最好不要用<=不然会出现while一直循环的情况(如果num+0==m)
while(num+mi[i].sn<m){
num+=mi[i].sn;
sum+=mi[i].p*mi[i].sn;
i++;
}
/*cout<<num<<" "<<sum<<endl;*/
if(num<m){sum += mi[i].p*(m-num);}
cout<<sum;
return 0;
}
服务次序问题
排队接水
多处最优服务次序问题的简化,先服务耗时短的人。
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
void solve(){
}
struct person{
int t,f;
};
bool cmp(const person a, const person b){
return a.t<b.t;
}
int main()
{
double ans=0;
int n;
person p[1005];
cin>>n;
for(int i=1; i<=n; i++){cin>>p[i].t;p[i].f=i;}
sort(p+1,p+1+n,cmp);
for(int i=1; i<=n; i++){
ans += (n-i)*p[i].t;
}
for(int i=1; i<=n; i++)
cout<<p[i].f<<" ";
printf("\n%.2lf", ans/n);
return 0;
}
删数问题
铺设道路
一开始想的是模拟,感觉时间复杂度是低于n^2的,TLE了。贪心的思路是画一下道路图,模拟一下前几步,总结下规律。例如4 3 2 5 3 5,第一天肯定能全填2个单位,变成2 1 0 3 1 3,2被填完肯定需要两天,且比2低的也能被填完,第一个3被填完需要3天,且比3低的也能被填完。第二个3被填完只需要2天。到此贪心策略就很简单了,只需要每一个上升段最大数减去最小数。
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
int main()
{
int n,a[100005];
long long int ans=0;
cin>>n;
for(int i=1; i<=n; i++)cin>>a[i];
for(int i=1; i<=n; i++){
if(a[i]>a[i-1])ans+=a[i]-a[i-1];
}
cout<<ans;
/*int n,a[100005],i,j,minn;cin>>n;
long long int ans=0,f;
for(i=1; i<=n; i++)cin>>a[i];
a[0]=0;a[n+1]=0;
while(1){
i=0;j=1;
f=ans;minn=0x7fffffff;
while(i!=n+1){
if(a[j]!=0){if(a[j]<minn)minn=a[j];j++;}
else if(a[j]==0 && j-i-1>0){for(int k=i+1; k<=j-1; k++)a[k]-=minn;ans+=minn;i=j;j++;}
else {i=j;j++;}
}
for(int i=1; i<=n; i++)cout<<a[i]<<" ";
cout<<endl;
if(f==ans)break;
}
cout<<ans;*/
return 0;
}
删数问题
删除一位数,剩下的数位数已经确定了,如果想让它最小,则需删除高位的大数
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
void solve(){
}
int main()
{
string a;
int k,i;
cin>>a>>k;
if(k>=a.size()){cout<<0;return 0;}
while(k--){
//找第一下降之前的数,删除它。如果没有就删除最大的数,表现在循环上就是删除最后一个
for(i=0; i<a.size()-1 && a[i]<=a[i+1]; i++);
a.erase(i,1);
}
while(a.size()>1 && a[0]=='0'){
a.erase(0,1);
}
cout<<a;
return 0;
}
日程安排问题
[ABC137D] Summer Vacation
这个题要与01背包分开,不仅要关注工作干不干,还要关注在哪天干。
贪心思想是遍历天数,每天选择当前能干的收益最高的工作。
package 贪心;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Summer_Vacation {
static class task{
int d,v;
}
public static void main(String[] args) {
//关注在那天应该干什么工作,要与01背包区分开。
int n,m,j=1;
long ans=0;
task[] task=new task[100005];
PriorityQueue<Integer> q=new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
Scanner in=new Scanner(System.in);
n=in.nextInt();m=in.nextInt();
for (int i = 1; i <= n; i++) {
task[i]=new task();
task[i].d=in.nextInt();
task[i].v=in.nextInt();
}
//按干完后得到收益的天数升序,好写
Arrays.sort(task, 1, 1 + n, new Comparator<Summer_Vacation.task>() {
@Override
public int compare(Summer_Vacation.task o1, Summer_Vacation.task o2) {
return o1.d-o2.d;
}
});
for (int i = 1; i <= m; i++) {
for(;j<=n && task[j].d<=i; j++)q.add(task[j].v);
if (!q.isEmpty())ans+=q.remove();
}
System.out.println(ans);
}
}
P2949 [USACO09OPEN]Work Scheduling G
跟上面那个题挺像的,不过这个工作必须在限定时间内完成,不容易转化为上面的形式。
贪心思路是按收益降序排列工作,然后遍历工作,选择合适的日子工作。不过因为数据量较大,还需要并查集降低时间复杂度。
题解
贪心排序
国王游戏
君王烽火戏诸侯只为博卿笑。。。
考虑最终最终状态的前一种状态,如何变成最终状态,设i为第i位大臣,j为第j位大臣,i < j,li,ri分别为大臣i的左右手数,lj,rj为大臣j的左右手数,k1为前i-1个大臣左手乘积,k2为i+1到j-1个大臣左手乘积。
不交换:
第i个大臣金币ans1=k1/ri。第j个大臣ans2=k1*li*k2/rj;
交换:
第i个大臣金币ans3=k1*lj*k2/ri。第j个大臣ans4=k1/rj;
ans=max(ans,ans1,ans2,ans3,ans4);
可知ans1<ans3;ans2>ans4;
ans=max(ans,ans3,ans4);
如果ans4>ans3可知li*ri > lj*rj,可得贪心策略li*ri < lj*rj;
//这里还用到了大精度乘法,除法,比较
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
struct person{
int l,r;
}p[10005];
string lc[10005],lr[10005];
void muti(string a, string b,int n){
int len1=a.size(),len2=b.size(),x,aa[20005],bb[20005],t[20005];
memset(aa,0,sizeof(aa));
memset(bb,0,sizeof(bb));
memset(t,0,sizeof(t));
for(int i=1; i<=len1; i++)aa[i]=a[len1-i]-'0';
for(int i=1; i<=len2; i++)bb[i]=b[len2-i]-'0';
for(int i=1; i<=len1; i++){
x=0;
for(int j=1; j<=len2; j++){
t[i+j-1]=aa[i]*bb[j]+x+t[i+j-1];
x=t[i+j-1]/10;
t[i+j-1]=t[i+j-1]%10;
}
t[i+len2]=x;
}
int len=len1+len2;
while(t[len]==0 && len>1){
len--;
}
for(int i=len; i>=1; i--){
// cout<<lc[n]<<" ";
lc[n]+=char(t[i]+'0');
}
}
void div(string a, int b,int n){
int len1=a.size(),x=0,aa[20005],t[20005];
memset(aa,0,sizeof(aa));
memset(t,0,sizeof(t));
for(int i=1; i<=len1; i++)aa[i]=a[i-1]-'0';
for(int i=1; i<=len1; i++){
t[i]=(x*10+aa[i])/b;
x=(x*10+aa[i])%b;
}
int len=1;
while(t[len]==0 && len<len1){
len++;
}
for(int i=0; i<=len1-len; i++)
lr[n]+=char(t[len+i]+'0');
}
bool cmp2(const person a, const person b){
return a.l*a.r<b.l*b.r;
}
string compare(string a, string b){
if(a.size()>b.size())return a;
else if(a.size()<b.size())return b;
else if(a>b)return a;
else return b;
}
int main()
{
//freopen("d://P1080_2.in","r",stdin);
int n;cin>>n;
for(int i=1; i<=n+1; i++)cin>>p[i].l>>p[i].r;
sort(p+2,p+2+n,cmp2);
lc[1]=to_string(p[1].l);
string ans="0";
for(int i=2; i<=n+1; i++){
muti(lc[i-1],to_string( p[i].l ),i);
div(lc[i-1],p[i].r,i);
ans=compare(ans,lr[i]);
}
cout<<ans;
return 0;
}
P3619 魔法
第一想法就是先将b为正的加起来,然后处理b为负或为0的魔法。但是剩下的魔法要按什么顺序施展呢,如果按b降序排列,貌似挺正确,但a值大的魔法就可能施展不了。对于这种确定排序策略的问题,可以从最终状态倒推,假设ai bi,ai+1 bi+1已经排好,那我们可以得知,ai+bi>ai+1,因为t>ai,并且t+bi>ai+1,所以再可得ai+bi>ai+1+bi+1。到此也就有了排序策略。
package 贪心;
import java.io.*;
import java.util.*;
public class P3619_魔法 {
static class m{
int t,b;
public m(int t, int b) {
this.t = t;
this.b = b;
}
public m() {
}
}
static int n,t,z,num1,num2,t1,t2;
static boolean ans=true;
static m a[]=new m[100005],b[]=new m[100005];
static PriorityQueue<m> q1=new PriorityQueue<>(new Comparator<m>() {
@Override
public int compare(m o1, m o2) {
return o2.t-o1.t;
}
}),q2=new PriorityQueue<>(new Comparator<m>() {
@Override
public int compare(m o1, m o2) {
return o2.t-o1.t;
}
});
public static void main(String[] args) throws IOException {
FastReader scan=new FastReader();
z=scan.nextInt();
while(z--!=0) {
ans=true;
num2 = 0;
num1=0;
n = scan.nextInt();
t = scan.nextInt();
for (int i = 1; i <= n; i++) {
t1 = scan.nextInt();
t2 = scan.nextInt();
if (t2 > 0) {
b[++num2] = new m();
b[num2].t = t1;
b[num2].b = t2;
} else {
a[++num1] = new m();
a[num1].t = t1;
a[num1].b = t2;
}
}
if (num2 != 0) {
Arrays.sort(b, 1, 1 + num2, new Comparator<m>() {
@Override
public int compare(m o1, m o2) {
return o1.t - o2.t;
}
});
for (int i = 1; i <= num2; i++) {
if (t > b[i].t) t += b[i].b;
else {
ans=false;
break;
}
}
}
Arrays.sort(a, 1, 1 + num1, new Comparator<m>() {
@Override
public int compare(m o1, m o2) {
return o2.t + o2.b - o1.t - o1.b;
}
});
for (int i = 1; i <= num1; i++) {
if (t > a[i].t) {
t += a[i].b;
} else {
ans=false;
break;
}
if (t <= 0) {
ans=false;
break;
}
}
if (ans) System.out.println("+1s");
else System.out.println("-1s");
scan.flush();
}
}
static class FastReader {
BufferedReader br;
StringTokenizer st;
PrintWriter out;
public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
out = new PrintWriter(new OutputStreamWriter(System.out));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
long nextLong() {
return Long.parseLong(next());
}
double nextDouble() {
return Double.parseDouble(next());
}
String nextLine() {
String str = "";
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
void print(Object obj) {
out.print(obj);
}
void println(Object obj) {
out.println(obj);
}
void println() {
out.println();
}
void flush() {
out.flush();
}
}
}
三国游戏
由题意可知人类不可能以最大的组合取胜于机器人,且机器人不可能获胜。
我们可以将每个组合值按升序排列,从大到小选择。那后续选择可以分成以下几种情况:
1、人类拥有当前组合的其中一个武将,显然人类可以以当前组合获胜。
2、机器人拥有当前组合的一个武将,不可能情况。
3、当前组合的武将是自由的,人类选择其中一个武将,此武将在后续选择中,最快被遇到。
4、机器人拥有当前组合的武将,不可能情况。
根据人类获胜情况可以写出ac代码:
//注释的代码由于复杂度高不能过,但是一种参考
#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
/*struct p{
int a,b;
long long int v;
};*/
/*bool cmp(const p a, const p b){
return a.v<b.v;
}*/
int main()
{
ios::sync_with_stdio(0);
int n;
long long int a[502][502];
/* p p1[124752];*/
//freopen("d://P1199_2.in", "r", stdin);
cin>>n;
for(int i=1;i<n;i++){
a[i][i] = 0;
for(int j=i+1;j<=n;j++){
cin>>a[i][j];
a[j][i] = a[i][j];
/* cin>>p1[++k].v;
p1[k].a=i;
p1[k].b=j;*/
}
}
//sort(p1+1,p1+1+k,cmp);
long long int ans=0;
for(int i = 1; i<=n; i++){
sort(a[i]+1,a[i]+1+n);
ans = max(ans,a[i][n-1]);
}
/*for(int i=k; i>=2; i--)
for(int j=i-1; j>=1; j--)
if((p1[i].a == p1[j].a || p1[i].a == p1[j].b || p1[i].b == p1[j].a || p1[i].b == p1[j].b) && p1[j].v > ans){ans = p1[j].v;break;}*/
cout<<1<<endl<<ans;
return 0;
}