文章目录
题目链接
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;
}
}