贪心学习小结-12138

贪心

贪心算法将问题分解为一个个的子问题,求每个子问题的解,然后合起来得最优解。
贪心必须对每个子问题的解都做当前最好的选择,而不关心未来的选择;动态规划则会根据之前的结果对当前进行选择。

题目小结

纯粹的贪心思想

[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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值