【Java】贪心专题训练

题目链接

HENAU冬令营-贪心专题
密码:202202100000

知识汇总

贪心/动归/搜索 的区别
从零开始学贪心算法

题目列表


快输模板

import java.io.*;
import java.util.*;
import java.util.regex.*;
public class Main {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

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

        boolean hasNext() {
            if (st != null && st.hasMoreTokens())
                return true;
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    }

    static PrintWriter out = new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(System.out)));
    /*
    核心代码区
    */
}

A - 外币兑换

找最大汇率,美金数量*汇率即得所获人民币最大值。

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        double n=sc.nextDouble();
        double a[]=new double[12];
        double max=0;
        for(int i=0;i<12;i++){
            a[i]=sc.nextDouble();
            if(a[i]>max)max=a[i];
        }
        out.printf("%.2f",max*n);
        out.flush();
    }

B - 删数问题

每次都取出第一个升序数列的末尾数字,直至取够指定次数。
注意:输出时需要判断第一位是否为0,结果不含前导0。
解法参考:删数问题

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        char c[]=(sc.next()).toCharArray();
        int k=sc.nextInt(),i;
        int len=c.length;
        while(k!=0) {
            i=0;//下标,循环求升序数列的最后一个值
            while(i<len-1&&c[i]<=c[i+1]) i++;
            while(i<len-1)//取数的过程
            {c[i]=c[i+1];i++;}
            len--;//取出后数字长度减1
            k--;//消耗掉一次取出次数
        }
        int flag=1;//判断当前数字是否为第一位
        for(i=0;i<len;i++)         //输出时要小心最高位是0的问题!处理输出……
        {
            if(c[i]=='0'&&i<len-1&&flag==1)  //如果即将输出的这一位是0且是最高位而且不是最后一个
                continue;
            else {
                out.printf("%c",c[i]);flag=0;
            }
        }
        out.flush();
    }

C - 股票买卖

状态转移方程:dp[i]=Math.max(dp[i-1],a[i]-min[i]);

dp[i]:以第i个数为节点的最大利润
min[i]:第i个数前的最小值

解法参考: 股票买卖

	static int a[],minl[],maxr[],dp1[],dp2[];
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        int t=sc.nextInt();
        int n;
        while(t-->0){
            n=sc.nextInt();
            ini(n);//初始化数组
            for(int i=1;i<=n;i++)a[i]=sc.nextInt();
            minl[1]=a[1];
            dp1[1]=0;
            for(int i=2;i<=n;i++)
            {
                minl[i]=Math.min(minl[i-1],a[i]);//找到这个数之前的最小值
                dp1[i]=Math.max(dp1[i-1],a[i]-minl[i]);//求出以这个数为节点的最大利润
            }
            maxr[n]=a[n];
            dp2[n]=0;
            for(int i=n-1;i>=1;i--)
            {
                maxr[i]=Math.max(maxr[i+1],a[i]);//找到这个数之后的最大值
                dp2[i]=Math.max(dp2[i+1],maxr[i]-a[i]);//求出以这个数为节点的最大利润
            }
            int ans=-1061109567;
            //保证所找两个值不重复
            for(int i=1;i<=n;i++)ans=Math.max(ans,dp1[i]+dp2[i]);
            out.printf("%d\n",ans);
        }
        out.flush();
    }
    public static void ini(int n){
        a=new int[n+1];
        minl=new int[n+1];
        maxr=new int[n+1];
        dp1=new int[n+1];
        dp2=new int[n+1];
    }

D - 数列分段

当前和大于最大子段和,分段数+1,当前和初始化为当前数字

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();//数组长度
        int m=sc.nextInt();//最大子段和
        int a[]=new int[n];
        int cursum=0,count=1;
        for(int i=0;i<n;i++){
            a[i]=sc.nextInt();
            cursum+=a[i];
            //核心代码
            if(cursum>m){
                count++;
                cursum=a[i];
            }
        }
        out.println(count);
        out.flush();
    }

E - 最大子段和

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        int num[]=new int[n+1];
        for(int i=0;i<n;i++)num[i]=sc.nextInt();
        long dp[]=new long[n];
        for(int i=0;i<n;i++)//找最大子段
            if(i>0&&dp[i-1]>0)
                dp[i]=dp[i-1]+num[i];
            else
                dp[i]=num[i];
        long max=0;
        for(int i=0;i<n;i++)
            if(max<dp[i])
                max=dp[i];
        out.println(max);
        out.flush();
    }

F - 活动安排问题

解法参考:51nod 1428

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        int s[]=new int[n];
        int e[]=new int[n];
        int i,k;
        for(i=0;i<n;i++) {
            s[i]=sc.nextInt();
            e[i]=sc.nextInt();
        }
        Arrays.sort(s);
        Arrays.sort(e);
        int sum=0;
        for(i=0,k=0;i<n;i++){
            //对于某一结束时间e[k],开始时间小于e[k]的个数,即需要教室的个数
            //相当于,对于升序排列的结束时间,统计活动时间重叠的总数
            if(s[i]<e[k]) sum++;
            else k++;
        }
        out.println(sum);
        out.flush();
    }

G - 线段

贪心策略为,优先保留结尾小且不相交的区间。在选择要保留的区间时,选择的区间结尾越小,余留给其他区间的空间就越大,就越能保留更多区间。

解法参考:Java版 视频6:25-13:30 & C++版

Java 得分70.0/100.0(TLE)

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        int line[][]=new int[n][2];
        for(int i=0;i<n;i++){
            line[i][0]=sc.nextInt();
            line[i][1]=sc.nextInt();
        }
        Arrays.sort(line, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]-o2[1];
            }
        });
        int sum=1;
        int pre=line[0][1];
        for(int i=1;i<n;i++){
            if(pre<=line[i][0]){
                sum++;
                pre=line[i][1];
            }
        }
        out.println(sum);
        out.flush();
    }

C++ 得分100.0/100.0 (AC)

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
typedef pair<int,int> Pair;

const int N=1e6+5;
int n,i;
Pair p[N];
bool cmp(Pair a,Pair b)
{
    return a.second<b.second;
}
int main()
{
    IOS;
    while(cin>>n){
        for(i=0;i<n;i++){
            cin>>p[i].first>>p[i].second;
        }
        sort(p,p+n,cmp);
        int pre=p[0].second,ans=1;
        for(i=1;i<n;i++){
            if(pre<=p[i].first){
                pre=p[i].second;
                ans++;
            }
        }
        cout<<ans<<endl;
        break;
    }
    return 0;
} 

H - 定价

重点在于优化枚举过程,某一区间内算得荒谬度最小值相同,而区间的判断与末尾0的个数有关。
解法参考: BZOJ4029-定价

	/**
     * 枚举时,一定区间内答案可能相同。
     * 例如,对于1200~1299区间,荒谬度最小值都是4。
    */
    static int cnt[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        int T=sc.nextInt();
        int l,r;
        while(T-->0) {
            l=sc.nextInt();
            r=sc.nextInt();
            int ans=0,min=20;
            for(int i=l;i<=r;i+=cnt[getInterval(i)]) {
                //结尾是5,荒谬度为2*a-1;否则为2*a
                int degree=2*getDigit(i)-getLast(i);
                if(min>degree) {
                    min=degree;
                    ans=i;
                }
            }
            out.println(ans);
        }
        out.flush();
    }
    //针对cnt[]数组求下标。即是末尾0的个数。
    public static int getInterval(int x){
        int i=0;
        while (x%10==0){
            x/=10;
            i++;
        }
        return i;
    }
    //求一个数的最后一位非零数的值。如果是5,则返回1,否则返回0。
    public static int getLast(int x){
        while(x%10==0)x/=10;
        int tp=x%10;
        if(tp==5)return 1;
        else return 0;
    }
    //求一个数不含末尾0的位数
    public static int getDigit(int x){
        int sum=0;
        while(x%10==0)x/=10;
        while(x!=0){
            x/=10;sum++;
        }
        return sum;
    }

I - 排队接水

当前所有人等待时间=当前接水时间*当前人数总和。
即排队时,越靠近前面的计算次数越多,因此越小的排在越前面得出的结果越小。

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        int t[]=new int[n];
        for(int i=0;i<n;i++)t[i]=sc.nextInt();
        Arrays.sort(t);
        long sum=0;
        for(int i=0;i<n;i++)sum+=(n-i)*t[i];
        out.println(sum);
        out.flush();
    }

J - 拼成最小的数 V2

任意两个字符串(将数字转为字符串)a和b,根据a+b和b+a的字典序进行比较。字典序较小则排在前面。
解法参考:51nod 2986

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        Integer id[]=new Integer[n];
        String s[]=new String[n];
        for(int i=0;i<n;i++){
            id[i]=i;
            s[i]=sc.next();
        }
        //比较的是下标,而不是字符串
        Arrays.sort(id,new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return (s[o1]+s[o2]).compareTo(s[o2]+s[o1]);
            }
        });
        for(int i=0;i<n;i++)out.print(s[id[i]]);
        out.flush();
    }

K - Huffman coding tree

题意 - 输出哈夫曼树的WPL(带权路径长度)
题目翻译:OpenJudge - Huffman编码树
解法参考:哈夫曼树的WPL值的计算
即WPL=(哈夫曼树)非叶子节点值之和

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        PriorityQueue<Integer> pq=new PriorityQueue<>
                (new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;//升序排列
            }
        });
        int w,wpl=0;
        for(int i=0;i<n;i++){
            w=sc.nextInt();
            pq.add(w);
        }
        int a,b,tp;//每次取最小的两个值,tp为a+b(即非叶子节点)
        while(pq.size()>=2){
            a=pq.poll();
            b=pq.poll();
            tp=a+b;
            wpl+=tp;
            pq.add(tp);
        }
        out.println(wpl);
        out.flush();
    }

L - 货币系统

为满足 (m,b) 与原来的货币系统 (n,a) 等价,则面额数组b是面额数组a的子数组;为使m 尽可能的小,则应使面额数组b中的任意元素不能被其他元素所表示。
题目链接:AcWing 532. 货币系统
解法参考:货币系统(完全背包问题)

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int T=sc.nextInt();
        int n,ans;
        int num[],dp[];
        while(T-->0){
            ans=0;
            n=sc.nextInt();
            num=new int[n];
            for(int i=0;i<n;i++)num[i]=sc.nextInt();
            //先排序, 然后判断每一个num[i]是否可以由之前的num[0]~num[i-1]构成
            //num[0]~num[i-1]可以选择无限个
            Arrays.sort(num);
            int tp=num[n-1];//num[n-1]=max
            dp=new int[tp+1];//0~max
            dp[0]=1;
            for(int i=0;i<n;i++){
                //判断当前的num[i]是否可以由之前的num[0]~num[i-1]表示出来
                //dp[i]表示面额为i的时候的方案数目
                if(dp[num[i]]==0) ans++;
                for(int j=num[i];j<tp+1;j++)dp[j]+=dp[j-num[i]];
            }
            out.println(ans);
        }
        out.flush();
    }

M - 反素数ant

解法参考:反素数ant(唯一分解定理的应用)

	//不超过N的最大的反质数:约数个数最多的数中的最小值
    static int n,ps[]={2,3,5,7,11,13,17,19,23,29,31},min,sum;
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        dfs(0,30,1,1);
        out.println(min);
        out.flush();
    }
    //idx引用质因子数组下标,limit限制当前因子的个数,cur表示当前数字,s表示约数个数
    public static void dfs(int idx,int limit,int cur,int s){
        //当cur的约数个数更多,或cur是约数个数最多的数中的更小值,则更新答案
        if(s>sum||s==sum&&cur<min){
            min=cur;sum=s;
        }
        /*举例:(模拟搜索过程)
        >>> n = 7 ; min = 6
        dfs先搜索到6,s = 1 * (1+1) * (1+1) = 4
        然后搜索到4,s = 1 * (2+1) = 3
         */
        for(int i=1;i<=limit;i++){
            if((long)cur*ps[idx]>n)break;
            cur*=ps[idx];
            dfs(idx+1,i,cur,s*(i+1));
        }
    }

N - 接水问题二

解法参考:接水问题二
关于排序:(第i个人的重要性是a[i],需要b[i]的时间来接水)

1在前,2在后 sum = a[1] * b[1] + a[2] * ( b[1] + b[2] )
2在前,1在后 sum = a[2] * b[2] + a[1] * ( b[1] + b[2] )
所以只需要让 a[2] * b[1] < a[1] * b[2] 即可。

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        int p[][]=new int[n][2];
        for(int i=0;i<n;i++){
            p[i][0]=sc.nextInt();//重要性
            p[i][1]=sc.nextInt();//等待时间
            if(p[i][0]==0||p[i][1]==0) {
                //时间或者重要性为0时,它可以不出现,避免对后面时间的计算造成影响
                i--;n--;
            }
        }
        Arrays.sort(p, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]*o2[0]-o1[0]*o2[1];
            }
        });
        long time=0,sum=0;//每个人的等待时间 & 等待时间乘以自己的重要性的总和
        for(int i=0;i<n;i++) {
            time+=p[i][1];
            sum+=time*p[i][0];
        }
        out.println(sum);
        out.flush();
    }

O - 部分背包

洛谷P2240
按单位价值降序的度量来判断是否选取

	public static void main(String[] args) {
        int n=sc.nextInt();//金币个数
        int t=sc.nextInt();//背包承重量
        Node node[]=new Node[n];
        for(int i=0;i<n;i++){
            node[i]=new Node(sc.nextInt(),sc.nextInt());
            node[i].avg=node[i].v*1.0/node[i].m;
        }
        Arrays.sort(node);
        double ans=0,weight=0;
        for(int i=0;i<n;i++){
            if(weight+node[i].m<=t){
                weight+=node[i].m;
                ans+=node[i].v;
            }else{
                ans+=node[i].avg*(t-weight);
                break;
            }
        }
        out.printf("%.2f",ans);
        out.flush();
    }
class Node implements Comparable<Node>{
    int m,v;//总重量 & 总价值
    double avg;
    public Node(int m,int v){
        this.m=m;this.v=v;
    }

    @Override
    public int compareTo(Node o) {
        return o.avg>avg?1:-1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值