【寒假训练/Java】贪心专题

题目链接

贪心专题-牛客链接
比赛密码:202302010213

题目列表

快输

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) 
    {
        out.flush();
    }

    static class FastReader{
        BufferedReader br;
        StringTokenizer st;
        String tmp;

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

        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 {
                tmp=br.readLine();
                st=new StringTokenizer(tmp);
            }catch(IOException e) {
                return false;
            }
            return true;
        }
    }

    static PrintWriter out=new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(System.out)));
    static FastReader sc=new FastReader();
}

A - 最小生成树

最小生成树:
一个有N个点的图,其最小生成树就是在这些边中选择N-1条出来,连接所有的N个点。这N-1条边的边权之和是所有方案中最小的。

题目中说明这张无向图是一张完全图,则可以先求得权值最小的顶点;根据前面的条件,通过该点连接其他所有点,这样得到的边权之和一定最小。

    public static void main(String[] args) 
    {
        long sum=0,min=(long)1e9+7,x,n;
        n=sc.nextInt();//输入n个点,最小生成树一共有n-1条边
        for(int i=0;i<n;i++)
        {
            x=sc.nextInt();
            sum+=x;
            min=Math.min(x,min);
        }
        out.println(sum+(n-2)*min);
        out.flush();
    }

B - [USACO 2006 Oct S]Fence Repair

说明部分可以理解为,为使代价最小,每次应选择长度最短的两根木头,并将二者长度之和作为整体存入。

即构造哈夫曼树的过程,使用优先队列减少排序时间。

    public static void main(String[] args) 
    {
        int n=sc.nextInt(),t;
        PriorityQueue<Integer> pq=new PriorityQueue<>(n);
        for(int i=0;i<n;i++) pq.add(sc.nextInt());
        long ans=0;
        while(pq.size()>=2) 
        {
            t=pq.poll()+pq.poll();
            ans+=t;
            pq.add(t);
        }
        out.println(ans);
        out.flush();
    }

C - money

题意是一共n个商店,每个商店可以有两个选择:花费ai价值买入 或者 卖出以获得ai价值;但当前拥有的物品数不能超过1,即不能连续买入;求最终获得的最大价值及最小交易次数。

解法为每次都在一个上升子串的低点买入,高点卖出(贪心思想),这样获得的价值最大,同时交易次数最少。

本质即是求不重叠上升子串的数目,最大价值为高点 - 低点之和,最小交易次数为子串数目*2(买入和卖出共交易两次)。

    public static void main(String[] args)
    {
        int T=sc.nextInt(),n;
        long[] a;//商店价值数组
        while(T-->0)//测试样例数目
        {
            n=sc.nextInt();//商店数目
            long pre=0,s=0,t=0;//上升子串的低点,总价值,交易次数
            a=new long[n];
            boolean f=false;//判断当前是否为上升子串
            a[0]=sc.nextLong();
            for(int i=1;i<n;i++)
            {
                a[i]=sc.nextLong();
                if(!f&&a[i]>a[i-1])
                {//进入上升子串,标记第一个值为低点
                    f=true;
                    pre=a[i-1];
                }
                if(f&&a[i]<a[i-1])
                {
                    s+=a[i-1]-pre;
                    f=false;
                    t++;
                }
            }
            if(f)
            {//最后一个上升子串持续到结尾的情况
                s+=a[n-1]-pre;
                t++;
            }
            out.println(s+" "+t*2);
        }
        out.flush();
    }

D - 大吉大利,今晚吃鸡——枪械篇

枚举+模拟

import java.io.*;
import java.util.*;

public class Main 
{
    public static void main(String[] args) 
    {
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext())
        {
            int n=sc.nextInt(),m=sc.nextInt(),p,k;//枪支数量和配件数量
            ArrayList<Integer>[] ls=new ArrayList[n];//存入枪支威力p,配件数量k,及k个配件种类
            for(int i=0;i<n;i++)
            {
                ls[i]=new ArrayList<>();
                p=sc.nextInt();
                k=sc.nextInt();
                ls[i].add(p);
                ls[i].add(k);
                for(int j=0;j<k;j++) ls[i].add(sc.nextInt());
            }
            double[] a=new double[1001];//配件可以为枪支提供的威力加成
            while(m-->0)
            {
                int q=sc.nextInt();//配件种类
                double b=sc.nextDouble();//威力加成
                a[q]=Math.max(a[q],b);
            }
            double ans=0,sum;
            for(int i=0;i<n;i++)
            {
               sum=1;
               p=ls[i].get(0);
               k=ls[i].get(1);
               for(int j=2;j<k+2;j++)
               {
                   sum+=a[ls[i].get(j)];
               }
               ans=Math.max(ans,p*sum);
            }
            out.printf("%.4f\n",ans);
        }
        out.flush();
    }

    static PrintWriter out = new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(System.out)));
}

E - 世界上最可爱的珂朵莉

    public static void main(String[] args)
    {
        int n=sc.nextInt();
        long x=sc.nextInt(),y=sc.nextInt(),ans=0;
        long[] a=new long[n],b=new long[n];
        for(int i=0;i<n;i++)
            a[i]=sc.nextLong();
        for(int i=0;i<n;i++)
            b[i]=sc.nextLong();
        Arrays.sort(a);
        Arrays.sort(b);
        for(int i=0;i<n;i++)
        {
            if(x==0||a[i]>=y)break;
            a[i]=y;
            x--;
        }
        Arrays.sort(a);
        for(int i=0;i<n;i++)
        {
            ans=Math.max(ans,b[i]-a[i]);
        }
        out.println(ans);
        out.flush();
    }

F - 牛牛的排序

    public static void main(String[] args)
    {
        int n=sc.nextInt(),max=0,min=1001;
        int[] a=new int[n],b=new int[n];
        for(int i=0;i<n;i++)
        {
            a[i]=sc.nextInt();
            b[i]=a[i];
            max=Math.max(a[i],max);
            min=Math.min(a[i],min);
        }
        Arrays.sort(b);
        //a数组本身就是升序排列,无序再次排序
        if(Arrays.equals(a,b))
            out.println(0);
        else
        {
            if(a[0]==min||a[n-1]==max)out.println(1);
            else if(a[0]==max&&a[n-1]==min)out.println(3);
            else out.println(2);
        }
        out.flush();
    }

G - 牛牛的朋友

在升序排列的数组中,枚举每一个点,假设当前点左侧全部右移,右侧全部左移,求得其中移动后左右端点的最小距离。

    public static void main(String[] args)
    {
        int n=sc.nextInt();
        int[] p=new int[n];
        for(int i=0;i<n;i++)p[i]=sc.nextInt();
        Arrays.sort(p);
        int x=sc.nextInt(),ans=p[n-1]-p[0];
        for(int i=0;i<n-1;i++)
        {
        	//左边界二选一:左端点右移后的位置 or 当前点右侧的端点左移后的位置
            int l=Math.min(p[0]+x,p[i+1]-x);
            int r=Math.max(p[n-1]-x,p[i]+x);
            ans=Math.min(ans,r-l);
        }
        out.println(ans);
        out.flush();
    }

H - 货币系统(完全背包)

  • 为满足 (m,b) 与原来的货币系统 (n,a) 等价,则面额数组b是面额数组a的子数组;
  • 为使m 尽可能的小,则应使面额数组b中的任意元素不能被其他元素所表示。

    又即通过完全背包判断a数组中的每一个元素是否可以通过若干其他元素相加得到,如果不能则表示该元素不可替代,需要被放入b数组。
    public static void main(String[] args) 
    {
        int T=sc.nextInt();
        int n,ans;
        int[] a,dp;
        while(T-->0)
        {
            ans=0;
            n=sc.nextInt();
            a=new int[n];
            for(int i=0;i<n;i++)a[i]=sc.nextInt();
            Arrays.sort(a);
            int tp=a[n-1];//a[n-1]即为max
            dp=new int[tp+1];//0~max
            dp[0]=1;
            //a[0]~a[i-1]可以选择无限个 - 完全背包
            for(int i=0;i<n;i++)
            {
                //判断当前的a[i]是否可以由之前的a[0]~a[i-1]表示出来
                //dp[i]表示面额为i的时候的方案数目
                if(dp[a[i]]==0) ans++;
                for(int j=a[i];j<tp+1;j++)dp[j]+=dp[j-a[i]];
            }
            out.println(ans);
        }
        out.flush();
    }

I - [CQOI2014]通配符匹配(DP&字符串哈希)

DP+字符串哈希:洛谷P3167 【[CQOI2014]通配符匹配】

    static long key=19260817;
    static long[] pre=new long[100010],mul=new long[100010],h=new long[20];
    static int n,cnt;
    static int[] sp=new int[20],stl=new int[20],dp[]=new int[15][100010];
    static char[] a=new char[100010],b[]=new char[15][100010];
    public static void main(String[] args) 
    {
        a=sc.next().toCharArray();
        int i,j,l,len=a.length;
        long t1;
        mul[0]=1;
        for(i=1;i<=100000;i++) mul[i]=mul[i-1]*key;
        if(a[0]=='*'||a[0]=='?') h[++cnt]=gethash(b[cnt],stl[cnt]=0);
        for(i=0;i<len;)
        {
            if(a[i]=='*'||a[i]=='?')
            {
                sp[cnt]=(sp[cnt]!=0||(a[i]=='*'))?1:0;
                i++;
            }
            j=0;cnt++;
            while(i<len&&a[i]!='*'&&a[i]!='?')
            {
                b[cnt][j]=a[i];
                i++;
                j++;
            }
            h[cnt]=gethash(b[cnt],stl[cnt]=j);
        }
        len++;
        if(a[len-2]=='*'||a[len-2]=='?')
        {
            cnt++;
            h[cnt]=0;
            sp[cnt]=stl[cnt]=0;
        }
        else sp[cnt]=0;
        n=sc.nextInt();
        for(l=1;l<=n;l++)
        {
            a=(sc.next()+" ").toCharArray();
            len=a.length-1;
            a[len]='$';
            len++;
            for(int[] ints:dp)Arrays.fill(ints,-1);
            dp[0][0]=2;pre[0]=0;
            for(i=0;i<len;i++) pre[i+1]=pre[i]*key+(long)a[i];
            for(j=0;j<=len;j++)
            {
                for(i=0;i<=cnt;i++)
                {//只有这里有差别
                    if(dp[i][j]==-1) continue;
                    if(dp[i][j]==1) dp[i][j+1]=1;
                    if(dp[i][j]==0)
                    {
                        dp[i][j]=2;
                        if(dp[i][j+1]==-1) dp[i][j+1]=2;
                        if(dp[i][j+1]==0) dp[i][j+1]=3;//判断赋2还是3
                        continue;
                    }
                    if(dp[i][j]==3)
                    {
                        dp[i][j]=2;
                        if(dp[i][j+1]==-1) dp[i][j+1]=2;
                        if(dp[i][j+1]==0) dp[i][j+1]=3;//判断赋2还是3
                    }
                    t1=pre[j+stl[i+1]]-pre[j]*mul[stl[i+1]];
                    if(t1==h[i+1])
                    {
                        dp[i+1][j+stl[i+1]]=Math.max(dp[i+1][j+stl[i+1]],sp[i+1]);
                    }
                }
            }
            if(dp[cnt][len]!=-1) out.println("YES");
            else out.println("NO");
        }
        out.flush();
    }

    static long gethash(char[] s,int len)
    {
        long re=0;int i;
        for(i=0;i<len;i++)
        {
            re*=key;
            re+=s[i];
        }
        return re;
    }

贪心解法参考:TLE

    static char[] p,s;//模式串,文本串
    public static void main(String[] args) 
    {
        p=sc.next().toCharArray();
        int T=sc.nextInt();
        while(T-->0)
        {
            s=sc.next().toCharArray();
            if(f()) out.println("YES");
            else out.println("NO");
        }
        out.flush();
    }
    static boolean f() 
    {
        int i=0, j=0, iStar=-1, jStar=-1, m=s.length, n=p.length;
        while (i<m) {
            if (j<n&&s[i]==p[j]||p[j]=='?')//可以匹配,双指针向后顺移
            {
                ++i;
                ++j;
            } else if (j<n&&p[j]=='*')//记录如果之后序列匹配不成功时, i和j需要回溯到的位置
            {
                iStar=i;//记录星号
                jStar=j++;//记录星号 并且j移到下一位 准备下个循环s[i]和p[j]的匹配
            } else if (iStar>=0)//发现字符不匹配且没有星号出现 但是istar>0 说明可能是*匹配的字符数量不对 这时回溯
            {
                i=++iStar;//i回溯到istar+1 因为上次从s串istar开始对*的尝试匹配已经被证明是不成功的(不然不会落入此分支) 所以需要从istar+1再开始试 同时inc istar 更新回溯位置
                j=jStar+1;//j回溯到jstar+1 重新使用p串*后的部分开始对s串istar(这个istar在上一行已经inc过了)位置及之后字符的匹配 
            } else return false;
        }
        while (j<n&&p[j]=='*') ++j;//去除多余星号
        return j==n;
    }

J - 牛牛学括号

题目给出的括号序列是合法的,那么左右括号一定匹配,且第一个括号为左括号。

维护一个,只存入左括号,遇到右括号则进行删除操作,可以删除的位置种类为栈的长度,因此要使当前结果✖栈长度。

    public static void main(String[] args)
    {
        int mod=(int)1e9+7;
        char[] s=sc.next().toCharArray();
        int l=s.length;
        long sum=1;
        Stack<Character> st=new Stack<>();
        for(int i=0;i<l;i++)
        {
            if(s[i]=='(') st.push(s[i]);
            else
            {
                sum=(sum*st.size())%mod;
                st.pop();
            }
        }
        out.println(sum);
        out.flush();
    }

K - [HAOI2008] 糖果传递(数学&贪心)

数学+贪心:解法参考

    public static void main(String[] args)
    {
        int n=sc.nextInt();
        long sum=0;
        long[] a=new long[n+1],b=new long[n+1],Sum=new long[n+1];
        for(int i=1; i<=n; i++)
        {
            a[i]=sc.nextLong();
            Sum[i]=Sum[i-1]+a[i];
            sum+=a[i];
        }
        long ave=sum/n;
        for(int i=1;i<n;i++)
            b[i]=Sum[n-1]-Sum[i-1]-(n-i)*ave;
        b[n]=0;
        Arrays.sort(b,1,n+1);
        long x=b[(n+1)/2];
        sum=0;
        for(int i=1;i<=n;i++)
            sum+=Math.abs(x-b[i]);
        out.println(sum);
        out.flush();
    }

L - 青蛙(二分)

    public static void main(String[] args)
    {
        int T=sc.nextInt();
        while(T-->0)
        {
            int n=sc.nextInt(),m=sc.nextInt(),d=sc.nextInt();
            int[] a=new int[m+2];
            for(int i=0;i<m+2;i++)
                a[i]=sc.nextInt();
            int l=1,r=m;//最多m只青蛙
            while(l<=r)
            {
                int mid=(l+r)>>1,f=1;
                //check部分,判断当前只数的青蛙能否跳到终点
                for(int i=0;i+mid<m+2;i++)
                    if(a[i+mid]-a[i]>d)
                    {
                        f=0;
                        break;
                    }
                if(f!=0) l=mid+1;
                else r=mid-1;
            }
            out.println(r);
        }
        out.flush();
    }

标解

在这里插入图片描述

    public static void main(String[] args)
    {
        int T=sc.nextInt();
        while(T-->0)
        {
            int n=sc.nextInt(),m=sc.nextInt(),d=sc.nextInt();
            m+=2;
            int[] a=new int[m+1];
            for(int i=1;i<=m;i++)
                a[i]=sc.nextInt();

            int r=1;
            int ans=2147483647;
            for(int i=1;i<=m;i++)
            {
                while(r<=m&&a[r]-a[i]<=d) r++;//当前位置可以跳的选择数量
                ans=Math.min(ans,r-i-1);//取每个位置可选择跳的最小值即为可过的青蛙
                if(r==m+1) break;
            }
            out.println(ans);
        }
        out.flush();
    }

M - 吃货(二分)

结构体 - 将食物按照价格升序排列,同等价格下,按照美味度降序排列。

美味度更新 - 当前价格之下有更高的美味度,则需要更新

二分 - 找到小于等于携带钱数的食物的最高美味度,因为之前进行了更新,只需要找到对应价格的食物即可。

import java.io.*;
import java.util.*;
class Node implements Comparable<Node>
{
    int d,c;//价格, 美味度
    Node(int d,int c)
    {
        this.d=d;
        this.c=c;
    }

    @Override
    public int compareTo(Node o) 
    {
        if(d==o.d)
            return o.c-c;//同等价格,美味度降序排列
        return d-o.d;//价格按升序排列
    }
}
public class Main 
{
    public static void main(String[] args)
    {
        int T=sc.nextInt();
        while(T-->0)
        {
            int n=sc.nextInt(),m=sc.nextInt();
            Node[] a=new Node[n];
            for(int i=0;i<n;i++)
                a[i]=new Node(sc.nextInt(),sc.nextInt());
            Arrays.sort(a);
            int t=0;//获得当前最高美味度
            for(int i=0;i<n;i++) //价格高的可以获得前面最高的美味度
            {
                if(a[i].c<t)
                    a[i].c=t;
                else
                    t=a[i].c;
            }
            while(m-->0)//m次询问
            {
                int ans=0;
                t=sc.nextInt();//钱数
                int l=0,r=n-1;//枚举下标,找价格小于等于t的物品的最大美味度
                while(l<=r)
                {
                    int mid=(l+r)>>1;
                    if(t<a[mid].d) r=mid-1;
                    else
                    {
                        if(ans<a[mid].c)
                            ans=a[mid].c;
                        l=mid+1;
                    }
                }
                out.println(ans);
            }
        }
        out.flush();
    }
	//快输 - 经测试不使用快输会TLE
    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)));
    static FastReader sc=new FastReader();
}