简谈贪心思维

本文讲述了如何运用贪心策略解决部分背包问题、线段覆盖优化和果树合并等典型问题,涉及快速读取、优先队列和简单数据处理技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一直在做贪心类型的题目

先来道简单的,也是非常典型的题:

# 【深基12.例1】部分背包问题

## 题目描述

阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 $N(N \le 100)$ 堆金币,第 $i$ 堆金币的总重量和总价值分别是 $m_i,v_i(1\le m_i,v_i \le 100)$。阿里巴巴有一个承重量为 $T(T \le 1000)$ 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?

## 输入格式

第一行两个整数 $N,T$。

接下来 $N$ 行,每行两个整数 $m_i,v_i$。

## 输出格式

一个实数表示答案,输出两位小数

## 样例 #1

### 样例输入 #1

```
4 50
10 60
20 100
30 120
15 45
```

### 样例输出 #1

```
240.00
```

#include<bits/stdc++.h>
using namespace std;
struct node
{
    double m,v,x;
}a[3000];
bool cmp(node q,node w)
{
    return q.x>w.x;
}
inline int read(){
	char c;
	bool flag=false;
	while((c=getchar())<'0'||c>'9')
	    if(c=='-') flag=true;
	int res=c-'0';
	while((c=getchar())>='0'&&c<='9')
	    res=(res<<3)+(res<<1)+c-'0';
	return flag? -res:res;
}
int main()
{
    int n;
    double h=0,t,s=0;
    n=read();
    t=read();
    for(int i=0;i<n;i++){
        a[i].m=read();
        a[i].v=read();
        a[i].x=a[i].v/a[i].m;
    }
    sort(a,a+n,cmp);
    for(int i=0;i<n;i++){
        if(h+a[i].m<=t){
            h+=a[i].m;
            s+=a[i].v;
        }
        else{
            s+=a[i].x*(t-h);
            break;
        }
    }
    printf("%.2lf\n",s);
    return 0;
}

用了个快读,贪婪的先选取性价比高的。

# 凌乱的yyy / 线段覆盖

## 题目背景

快 noip 了,yyy 很紧张!

## 题目描述

现在各大 oj 上有 $n$ 个比赛,每个比赛的开始、结束的时间点是知道的。

yyy 认为,参加越多的比赛,noip 就能考的越好(假的)。

所以,他想知道他最多能参加几个比赛。

由于 yyy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 $2$ 个及以上的比赛。

## 输入格式

第一行是一个整数 $n$,接下来 $n$ 行每行是 $2$ 个整数 $a_{i},b_{i}\ (a_{i}<b_{i})$,表示比赛开始、结束的时间。

## 输出格式

一个整数最多参加的比赛数目。

## 样例 #1

### 样例输入 #1

```
3
0 2
2 4
1 3
```

### 样例输出 #1

```
2
```

## 提示

- 对于 $20\%$ 的数据,$n \le 10$;
- 对于 $50\%$ 的数据,$n \le 10^3$;
- 对于 $70\%$ 的数据,$n \le 10^{5}$;
- 对于 $100\%$ 的数据,$1\le n \le 10^{6}$,$0 \le a_{i} < b_{i} \le 10^6$。

类比为线段问题:

在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分,问最大的k为多少。

最左边的线段放什么最好?

显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少

其他线段放置按右端点排序,贪心放置线段,即能放就放

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int start,end;
}a[1200000];
bool cmp(node x,node y)
{
    return x.end<y.end;
}
int main()
{
    int n,s=0,v=0;
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d%d",&a[i].start,&a[i].end);
    sort(a,a+n,cmp);
    for(int i=0;i<n;i++){
        if(a[i].start>=v){
            v=a[i].end;
            s++;
        }
    }
    printf("%d\n",s);
    return 0;
}

接下来上点有难度的(虽然难度不高)

# [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

## 题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 $n-1$ 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 $1$ ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 $3$ 种果子,数目依次为 $1$ , $2$ , $9$ 。可以先将 $1$ 、 $2$ 堆合并,新堆数目为 $3$ ,耗费体力为 $3$ 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 $12$ ,耗费体力为 $12$ 。所以多多总共耗费体力 $=3+12=15$ 。可以证明 $15$ 为最小的体力耗费值。

## 输入格式

共两行。  
第一行是一个整数 $n(1\leq n\leq 10000)$ ,表示果子的种类数。  

第二行包含 $n$ 个整数,用空格分隔,第 $i$ 个整数 $a_i(1\leq a_i\leq 20000)$ 是第 $i$ 种果子的数目。

## 输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 $2^{31}$ 。

## 样例 #1

### 样例输入 #1

```

1 2 9
```

### 样例输出 #1

```
15
```

## 提示

对于 $30\%$ 的数据,保证有 $n \le 1000$:

对于 $50\%$ 的数据,保证有 $n \le 5000$;

对于全部的数据,保证有 $n \le 10000$。

做这道题时注意我们要使用优先队列,如果直接sort排序100%会超时。接着就是一道水题了,直接套公式,模板题。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,x,ans=0;
    priority_queue<int,vector<int>,greater<int>>q;
    cin>>n;
    for(int i=0;i<n;i++)cin>>x,q.push(x);
    while(q.size()>=2){
        int a=q.top();q.pop();
        int b=q.top();q.pop();
        ans+=a+b;
        q.push(a+b);
    }
    cout<<ans<<endl;
    return 0;
}

# [NOIP2018 提高组] 铺设道路

## 题目背景

NOIP2018 提高组 D1T1

## 题目描述

春春是一名道路工程师,负责铺设一条长度为 $n$ 的道路。 

铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 $n$ 块首尾相连的区域,一开始,第 $i$ 块区域下陷的深度为 $d_i$ 。 

春春每天可以选择一段连续区间 $[L,R]$ ,填充这段区间中的每块区域,让其下陷深度减少 $1$。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 $0$ 。 

春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变为 $0$ 。

## 输入格式

输入文件包含两行,第一行包含一个整数 $n$,表示道路的长度。 第二行包含 $n$ 个整数,相邻两数间用一个空格隔开,第 $i$ 个整数为 $d_i$ 。

## 输出格式

输出文件仅包含一个整数,即最少需要多少天才能完成任务。

## 样例 #1

### 样例输入 #1

```
6   
4 3 2 5 3 5
```

### 样例输出 #1

```
9
```

## 提示

【样例解释】

一种可行的最佳方案是,依次选择: 
$[1,6]$、$[1,6]$、$[1,2]$、$[1,1]$、$[4,6]$、$[4,4]$、$[4,4]$、$[6,6]$、$[6,6]$。   

【数据规模与约定】

对于 $30\%$ 的数据,$1 ≤ n ≤ 10$ ;    
对于 $70\%$ 的数据,$1 ≤ n ≤ 1000$ ;     
对于 $100\%$ 的数据,$1 ≤ n ≤ 100000 , 0 ≤ d_i ≤ 10000$ 。

这题带一点差分思维,需要递归找找规律。规律是啥我就不详细说了,这篇主要是将贪心思想,其实这题我感觉就和贪心没啥关系。

找到规律之后就非常简单啦。(感觉数组都没必要开)

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,s=0;
    cin>>n;
    int a[200000];
    a[0]=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)if(a[i]>a[i-1])s+=a[i]-a[i-1];
    cout<<s<<endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值