蓝桥杯_贪心

文章目录

  • 由数据范围反推算法复杂度以及算法内容
  • 区间问题
    • 905. 区间选点
    • 907. 区间覆盖
    • 908. 最大不相交区间数量
  • Huffman树
    • 148. 合并果子
  • 913.排队打水
  • 104. 货仓选址
  • 125.耍杂技的牛

#include 
using namespace std; 

由数据范围反推算法复杂度以及算法内容

链接:https://www.acwing.com/blog/content/32/
一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 107~108为最佳。
n≤30, 指数级别, dfs+剪枝,状态压缩dp
n≤100 => O(n3),floyd,dp,高斯消元
n≤1000 => O(n2),O(n2logn),dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford
n≤10000 => O(n3/2),块状链表、分块、莫队
n≤100000 => O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树
n≤1000000 => O(n), 以及常数较小的 O(nlogn)O(nlogn) 算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa
n≤10000000 => O(n),双指针扫描、kmp、AC自动机、线性筛素数
n≤109 => O(n1/2),判断质数
n≤1018 => O(logn),最大公约数,快速幂,数位DP
n≤101000 => O((logn)2),高精度加减乘除
n≤10100000 => O(logk×loglogk),k表示位数O(logk×loglogk),k表示位数,高精度加减、FFT/NTT

时间复杂度分析
long long 类型级别 1e18
动态规划问题计算量 = 状态数量 * 状态转移的计算量
1 Byte = 8 bit 【bit二进制位数】
1 KB = 1024 Byte
1 MB = 1024 KB = 1024 * 1024 Byte
1 GB = 1024 MB = 1024 * 1024 * 1024 Byte
1 TB = 1024 GB

例子:
64MB = 226Byte = 1024*1024 * 16 ~= 可以开1600万数组空间
操作系统优化,开了没有用是允许超过限制的,但在要用的时候需分配一块空间 ,开了且用了才会超

//贪心和DP - - 难
//经典贪心问题

区间问题

905. 区间选点

给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
数据范围
1≤N≤105
-109≤ai≤bi≤109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

思路:
1.将每个区间按右端点从小到大排序
2.从前往后依次枚举每个区间 , 如果当前区间中已经包含点,就跳过 ,否则,选择当前区间的右端点


#include
#include
using namespace std;
const int N=1e5+10;
int n;
struct range
{
    int l,r;
    bool operator<(const range &w)const   //重载‘<’,对区间右端点进行· 排序
    {
        return r<w.r;
    }
}range[N];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
   		int l,r;
        scanf("%d%d",&l,&r);
        range[i]={l,r};
    }
    sort(range,range+n);  //排序
    int res=0,ed=-2e9;
    for(int i=0;i<n;i++)
    {
        if(range[i].l>ed)
        {
            res++;
            ed=range[i].r;
        }
    }
    printf("%d",res);
    return 0;
}






907. 区间覆盖

给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 ?1。
输入格式
第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。
第二行包含整数 N,表示给定区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出 -1。
数据范围
1≤N≤10 ^5,
-10 9≤ai≤bi ≤10 9 ,
-10 9≤s≤t≤10 9
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2

思路:
1.将所有区间按左端点从小到大排序
2.从前往后依次枚举每个区间,在所有能覆盖 start 的区间中,选择右端点最大的区间然后将 start 更新成右端点的最大值

908. 最大不相交区间数量

给定N个闭区间[ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,biai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取区间的最大数量。
数据范围
1≤N≤105,
-10^9≤ai ≤bi ≤109输入样例:
3
-1 1
2 4
3 5
输出样例:
2

引用~思路:
①将每个区间按右端点从小到大排序
②从前往后依次枚举每个区间 如果当前区间中已经包含点,则直接pass 否则,选择当前区间的右端点
类似排节目 一个节目越早结束 一场晚会就越能表演更多的节目




#include
using namespace std;

const int N = 100010;

struct Range {
	int l, r;
}range[N];

bool cmp(struct Range a, struct Range b) {
	return a.r < b.r;
}

int main() {
	int n;cin >> n;
	for (int i = 0;i < n;++i) {
		int l, r;scanf("%d%d", &l, &r);
		range[i] = { l,r };
	}
	sort(range, range + n, cmp);
	int res = 0, ed = -2e9;

	for (int i = 0;i < n;++i) {
		if (range[i].l > ed) {
			++res;
			ed = range[i].r;
		}
	}

	cout << res << endl;
	return 0;
}

Huffman树

148. 合并果子

在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。
达达决定把所有的果子合成一堆。
每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。
可以看出,所有的果子经过 n?1 次合并之后,就只剩下一堆了。
达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。
假定每个果子重量都为 1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3 种果子,数目依次为 1,2,9。
可以先将 1、2 堆合并,新堆数目为 3,耗费体力为 3。
接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12,耗费体力为 12。
所以达达总共耗费体力=3+12=15。
可以证明 15 为最小的体力耗费值。
输入格式
输入包括两行,第一行是一个整数 n,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 ai 是第 i 种果子的数目。
输出格式
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。
输入数据保证这个值小于 231。
数据范围
1≤n ≤10000,
1≤ai ≤20000
输入样例:
3
1 2 9
输出样例:
15

思路:
每次选取最小的两堆合并成一堆,最终合并成一堆。
优先队列 == 小根堆 逆序从大到小 定义参数为固定要写的,要用就要背
priority_queue heap;




#include 
#include 
#include 

using namespace std;

int main()
{
    int n;
    scanf("%d", &n);
	//优先队列 == 小根堆  逆序从大到小 定义参数为固定要写的,要用就要背
    priority_queue<int, vector<int>, greater<int>> heap;
    while (n -- )//读入数据入队(优先队列)堆
    {
        int x;
        scanf("%d", &x);
        heap.push(x);
    }

    int res = 0;
    while (heap.size() != 1) //遍历堆
    {
        int a = heap.top(); heap.pop();//最小的两个在队头【优先队列自动完成】
        int b = heap.top(); heap.pop();
        res += a + b;
        heap.push(a + b); // 注意这里是push a+b【新得到的堆放入队列】,不是push res(最终体力)
    }

    printf("%d\n", res);
    return 0;
}

913.排队打水

有 n 个人排队到 1 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?
输入格式
第一行包含整数 n。
第二行包含 n个整数,其中第 i 个整数表示第 i 个人装满水桶所花费的时间 ti。
输出格式
输出一个整数,表示最小的等待时间之和。
数据范围
1≤n ≤105
1≤ti ≤104
输入样例:
7
3 6 1 4 2 5 7
输出样例:
56

思路 :假设一种做法,证明这种做法的正确性,如反证法,调整法
每个人等没有人打水 才可以用,轮流打水 ,【让打水时间最多的人最后打水,它就不用让别人等他打水的时间 】
最优解: sort(a,a+n) , a[i] * (n-i -1 )
最前面后面的人一起等待a[i]时间,每次打完水,等待的人减一



#include

const int N = 100010;

typedef long long LL; 
int n;
int t[N];


int test_01()
{
	scanf("%d",&n);
	for(int i = 0;i < n;i++) scanf("%d",&t[i] );
	
	sort(t,t+n);
	
	LL res = 0;
	for(int i = 0;i < n;i++) res += t[i]*(n - i - 1);
	
	cout << res << endl;   //prnitf("%lld",res);
	
	return 0;
}

104. 货仓选址

在一条数轴上有 N 家商店,它们的坐标分别为 A1~AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数 N。
第二行 N 个整数 A1~AN。
输出格式
输出一个整数,表示距离之和的最小值。
数据范围
1≤N ≤100000,
0≤Ai ≤40000
输入样例:
4
6 2 9 1
输出样例:
12

思路:
求f(x) = |x[1] - x| + |x[2] - x| + |x[3] - x| + |x[4] - x| + |x[5] - x| + |x[6] - x| + … +|x[n] - x|
只看其中两项
绝对值不等式:
|x - a| + | x - b | >= |a - b| x在[a,b]区间内 , 等号成立
即等号成立时最小,可以发现x处于每一项的中位数,就都可以取最小值




#include
using namespace std;
#include

const int N = 100010;

int n;
int a[N];

int test_02()
{
	
	scanf("%d",&n);
	for(int i = 0;i < n;i ++ ) scanf("%d",&a[i]);
	
	sort( a , a + n); //数轴排序 
	
	int res = 0;
	for(int i = 0;i < n ;i ++) res += abs(a[i] - a[n / 2]);  //最短距离为 与中位数距离  
	
	cout <<  res << endl;
	
	
	return 0;
}

//很多问题由不等式推出,如调和不等式… 推导出贪心或者DP的关键

125.耍杂技的牛

题目描述:
农民约翰的N头奶牛(编号为1…N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。奶牛们不是非常有创意,只提出了一个杂技表演:叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。这N头奶牛中的每一头都有着自己的重量Wi以及自己的强壮程度Si。一头牛只撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
输入格式
第一行输入整数N,表示奶牛数量。接下来N行,每行输入两个整数,表示牛的重量和强壮程度,第i行表示第i头牛的重量Wi以及它的强壮程度Si。
输出格式
输出一个整数,表示最大风险值的最小可能值。
数据范围
1≤N≤50000,
1≤Wi≤10,000,
1≤Si≤1,000,000,000
输入样例:
3
10 3
2 5
3 3
输出样例:
2

贪心得到的答案 >= 最优解
思路:
下面的牛的危险系数 = 上面牛的重量总和 - 自身的强壮值
取最小值,给定顺序【可先知最重的放最下面】
最优解:按照wi + si 从小到大的顺序排为最优解, 最大的危险系数一定是最小的 【类似题:提高组-国王游戏-乘除】
证明: 最优解 >= 贪心得到的答案 >= 最优解 - - - > 贪心 = = 最优解
推导过程…




#include
using namespace std;

#include 
#include

typedef pair<int ,int> PII;  //w + s 排序 
const int N = 50010;
int n;
PII cow[N];

int test_03()
{
	scanf("%d",&n);
	for(int i = 0;i < n;i++)
	{
		int w,s;
	 	scanf("%d%d",&w,&s);
		cow[i].first = w + s,cow[i].second = w;  	//C++11  cow[i] = {w + s, w};		
	}
	
	sort(cow,cow + n); //按照first排序 ,从小到大    
		
	int res = 2e-9,sum = 0;
	for(int i = 0;i < n;i++)
	{
		int w = cow[i].second, s = cow[i].first - w; 
		res = max(res, sum - s);//取危险系数的最大值   --过程中尽量减小最大值  
		sum += w;		
	}	
	
	cout << res << endl;	
	return 0;
}




int main() {
	
	test_03();
	
	return 0;
	
}

你可能感兴趣的:(算法,蓝桥杯,c++)