acm选修课——贪心技术

贪心技术简介

局部解是最终解的一部分
在这个基础上继续解更多的解

优点:不用回溯,效率高

案例1:桥

POJ题目链接
题意:
有N个人过桥,每个人单独过桥花 t i 时间
天黑要手电筒,他们只有一个手电筒
这桥最多容纳两人同时过,取慢者的时间计
求过桥最短时间
(1 ≤ N ≤ 50) (ti are integers from 1 to 100).

一道有名的老题,曾经公司面试题,一万人面试只有4个人做出来

思路:
(先排序)

贪心想法1:
是不是可以让用时最短的一一把人都送过去呢?
这样子时间就是
∑ \sum ti (i从2到n)+(n-2)t1

问题是
这样子就让时间占的多的都去了一遍
不如让时间占的多的一起上,两次换成1次
那么我们让时间最短次短的送手电筒,先一起去之后分开回来

这样子把最短次短的送过去时间是
t2+t1+tn+t2

但是我们不能不证明,仅凭一般感觉下结论,后者一定好

比较:
前者定量
送一个:
tn+t1
送两个:
tn+t1+tn-1+t1
后者定量:
送两个:
t2+t1+tn+t2
(送一个也这样,就浪费了时间)

可以看到,
送一个,第一种好
送两个,比较
tn-1+t1

2 * t2
一般是tn-1+t1大,但是还是要判断的

最后只有三个时
由于
t2+t1比2 * t2 小,不用特判

但是我们还可以发现
只要开始
tn-1+t1<2 * t2
则之后就更小了
所以我们只要判断一次
即开始按贪心2每次减2
之后按贪心1每次减1

AC代码:
开始样例错了
是没发现我们只要判断一次,最后几种情况乱了
又错
是最后只有2个人时只要取t[2]
wa了一次,注意特判只有一个时,取t[1]

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<algorithm>
//#include<string>
//#include<sstream>
//#include<vector>
//#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
//#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define vi vector <int> 
#define vii vector <vi> 
#define st first
#define nd second
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
#define N 55
int t[N];
int main(){
	fastio
	int n;
	cin>>n;
	t[0]=0;
	_forplus(i,1,n){
		cin>>t[i];
	}
	if(n==1){
		cout<<t[1]<<endl;
		return 0;
	}
	sort(t+1,t+1+n);
	int sum=0;
	while(n>2){//最后两个可以一起过 
		if(2*t[2]>t[1]+t[n-1]){
			break;
		}//跳到第一种贪心
		sum+=t[1]+2*t[2]+t[n];
		n-=2;
		//第二种贪心 
	}
	while(n>2){
		sum+=t[1]+t[n];
		n--;
	} 
	sum+=t[2];
	cout<<sum<<endl;
	return 0;
}

案例2:安放雷达

POJ题目链接
题意:
有N个海岛,雷达射程为d
给出海岛坐标
求,要在海岸线上至少装多少雷达
如果不能,输出-1

有多组样例,输出标出case i
输入 N=0 d=0 结束

(1<=n<=1000)

分析:
贪心法找最大包含,之前写过一个二分+贪心的题
https://blog.csdn.net/qq_51945248/article/details/115607474

先勾股定理求出
各岛对应海岸线上的雷达安放左右范围
(如果没有范围,就是不存在了,但数据还是要读完)
从左向右看各岛
如果范围内有雷达,就可以
如果没有,就在最右点放一个,因为这样最可能在之后更右边的岛的范围内
雷达数也加1

AC代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<algorithm>
//#include<string>
//#include<sstream>
//#include<vector>
//#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
//#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define st first
#define nd second
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
#define N 1005
pii dao[N];
pdd an[N];
istream& operator >> (istream&input,pii&t){
	input>>t.first>>t.second;
	return input;
}//1.重载参数 2.t不是指针 
bool cmp(pdd a,pdd b){
	return a.second<b.second;
}
int main(){
	fastio
	int n,d,cnt=0;
	while(cin>>n>>d,n||d){
		int flag=1;//默认都合理 
		_forplus(i,1,n){
			cin>>dao[i];
			if(abs(dao[i].second)>d)flag=0;
		}
		if(!flag){
			cout<<"Case "<<++cnt<<": -1"<<endl;
		}else{
			_forplus(i,1,n){
				int x=dao[i].first;
				double dx=sqrt(d*d-dao[i].second*dao[i].second);//不要忘了sqrt,到处对要用心 
				an[i].first=x-dx; 
				an[i].second=x+dx; 
			}
			sort(an+1,an+1+n,cmp);
			int ct=1;
			double now=an[1].second;
			_forplus(i,2,n){
				if(an[i].first>now){
					ct++;
					now=an[i].second;
				}
			}
			cout<<"Case "<<++cnt<<": "<<ct<<endl;
		}
	}
	return 0;
}

还有复习一下重载:
istream& operator >> (istream&input,pii&t){
	input>>t.first>>t.second;
	return input;
}//1.重载参数 2.t不是指针 

WA了两次,原来是想到sqrt就以为自己写上了,结果没有写上
真是低级错误,要用心
多亏了评论区的这个数据
不过其实就算是拿样例调试,数据也有不同的

2 5
-3 4
-6 3


4 5
-5 3
-3 5
2 3
3 3

20 8
-20 7
-18 6
-5 8
-21 8
-15 7
-17 5
-1 5
-2 3
-9 6
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 7
9 6
10 5
0 0

2 3
0 2
2 3

2 3
0 2
1 3

3 3
1 2
-3 2
2 4

8 5
2 4
-4 4
-3 3
-3 1
-3 0
-1 0
0 5
6 0

3 0
1 2
-3 1
2 1

3 2
1 2
-3 1
2 1

1 2
0 2


2 3
0 2
2 3

4 -5
4 3
4 3
2 3
6 -9



3 -3
1 2
-3 2
2 1

6 2
1 2
1 2
1 2
-3 1
2 1
0 0

1 2
0 2

2 3
0 2
1 3

3 10
1 10
2 3
4 5

3 5
1 10
2 3
4 5

4 7
1 10
2 3
4 5
0 0

3 9
1 10
2 3
4 5
0 0

================结果
Case 1: 1
Case 2: 2
Case 3: 4
Case 4: 1
Case 5: 1
Case 6: -1
Case 7: 3
Case 8: -1
Case 9: 2
Case 10: 1
Case 11: 1
Case 12: -1
Case 13: -1
Case 14: 2
Case 15: 1
Case 16: 1
Case 17: 1
Case 18: -1
Case 19: -1
Case 20: -1
挥着翅膀的鳖 献上。。。
d<=0不需要判断
y<=0 不需要判断
把每个岛屿来当做雷达的圆心,半径为d,做圆,与x轴会产生两个焦点L和R,这就是一个区间;
首先就是要把所有的区间找出来,然后x轴从左往右按L排序,再然后就是所谓的贪心把那些互相重叠的区间去掉就行了,区间也就是雷达;


案例3:合并果子(相当于哈夫曼树,哈夫曼编码)

HOJ(湖南大学OJ)题目,NOI的中文题,没有校园网进不来的同学可到洛谷一叙
题意:
有n堆果子,
每堆果子质量 a i
(1 <= n <= 10000)
(1 <= ai <= 20000)

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和
所有的果子经过n-1次合并之后,就只剩下一堆了
求最小的体力耗费值
保证这个值小于2的31次方

分析:
不难想到,排序不等式,
轻的多搬,重的少搬
而合并后是新的一堆
所以每次合并就是把最轻和次轻的合并

数据结构上:
为了方便,可以弄优先队列

为了高效,可以用两个数组,不用排序
一个a存原堆,从小到大排序
一个b存产生的堆,新产生的比老产生的大,因为原来轻的合并了,在变重了
每次只要判断从
a取2个,
a,b各取1个,
b取2个
里取最小值m,
相应地修改位置指针
sum+=m
同时m加到b里

进行n-1次,这是固定的

拓展:
哈夫曼树差不多就是这样
当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”
在这里,我们有N堆果子,正是在找带权路径长度最小的最优二叉树
方法差不多就是这样,多存一下节点就好

应用之一:哈夫曼编码
拿e,这个英语里用的最多的字母,用1个字节特定存储
拿z,这个英语里用的最少的字母,用8个字节特定存储
因为e用的多,所以经常在节省,总存储是最短的
这和传统的26个需要22222,5个字节不同
当然不一定是这个规则,是看该文本的各字符出现频率决定的
静态的就根据整个文本统计频率构造哈夫曼树
动态的是实时的,每一个字符来源于之前已有字符的哈夫曼树

回到正题:
我们为了减少判断边界
可以利用通用的判断规则
如这里的“墙”:a[n+1],a[n+2]=INF,就不会让a[n]+a[n+1],a[n+1]+a[n+2]有机会
b也是,开始都是INF,如果现在是第i次,合成就是i-1个,就不会让b[i-1]+b[i]有机会
而总共的n-1次,不受影响的

const int INF=(int)pow(2,30);//比0x3f3f3f3f大,不能代替 

但是我为了方便,还是用了优先队列。
——但是超时了,,,

priority_queue<int,vector<int>,greater<int> >q;
值得注意的是,优先队列的写法,默认降序,greater < int > 改变顺序
sort ,默认升序, greater< int > () 改变顺序
注意共同点是改变顺序,不同点是默认排序不同,
且语法上sort的比较多一个()
因为sort传的是真函数,priority_queue传的是仿函数,实际上是结构体

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<algorithm>
//#include<string>
//#include<sstream>
//#include<vector>
//#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define st first
#define nd second
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
#define N 100004
priority_queue<int,vector<int>,greater<int> >q;
int main(){
	fastio
	int n;
	cin>>n;
	int t;
	_forplus(i,1,n){
		cin>>t;
		q.push(t);
	}
	int sum=0,ta,tb,tc;
	_forplus(i,1,n-1){
		ta=q.top();q.pop();
		tb=q.top();q.pop();
		tc=ta+tb;
		sum+=tc;
		q.push(tc);
	}
	cout<<sum<<endl;
	return 0;
}

看来还是要减少排序时间啊。
Accepted 1212KB 62ms :

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<algorithm>
//#include<string>
//#include<sstream>
//#include<vector>
//#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define st first
#define nd second
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define INF 0x3f3f3f3f
//const int INF=(int)pow(2,30);
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
#define N 100004
int a[N],b[N]; 
int main(){
	fastio
	int n;
	cin>>n;
	_forplus(i,1,n+2){
		b[i]=INF;
	}
	a[n+1]=a[n+2]=INF;
	_forplus(i,1,n){
		cin>>a[i];
	}
	sort(a+1,a+1+n);
	int sum=0,tc,pa=1,pb=1,cntb=1;
	_forplus(i,1,n-1){
		int flag=0;//0:2a,1:ab,2:2b
		tc=a[pa]+a[pa+1];
		if(tc>a[pa]+b[pb]){
			flag=1;
			tc=a[pa]+b[pb];
		}
		if(tc>b[pb]+b[pb+1]){
			flag=2;
			tc=b[pb]+b[pb+1];
		}
		sum+=tc;
		b[cntb++]=tc;//别忘了加这个 
		if(flag==0){
			pa+=2;
		}else if(flag==1){
			pa++,pb++;
		}else{
			pb+=2;
		}
	}
	cout<<sum<<endl;
	return 0;
}

但是要声明一个问题:
我是不应该用0x3f3f3f3f的,它更小
自己都可以找到错误:

2
1073741823 1073741823
正确:2147483646
错误:2122219134
*10737418232^30-1

只能说测试数据不够给力
之所以没有用INF=230是因为越界
不过我们可以用INF=230-1吗?
给的是小于231
即可以给230,230-1的测试用例
还是不行
建议改unsigned int 或long long 后用INF=230

——后纠:
不是的,测试数据范围就是一堆10000个,最多20000堆,不可以有上面的数据的、
就算再这么合并,最后也是200000000个,一半也是100000000,并且最后也不可能到这么大结果还不超过231

案例4:判断一个数是否可以分解为阶乘和

POJ题目链接
题意:
给你一个非负数n,问能否被分解乘若干个阶乘的和,输出"YES" or “NO”
(n <= 1,000,000)
输入-1结束

分析:
首先0是个特殊的
等下特判交两次解决

现在有三种方法:

  1. 打表,反正1,000,000内阶乘不多,每一个组合都是可以,没有弄到就不可以
  2. 动态规划01背包,
    对单个n,其容积为n,放入一个物品t!
    已知结果为m时可以,则m+t!也可以,只要m+t!不越界
    首先认为0是可以的,从0!开始加,到t!,也相当于打了一个表
    最后看n是否刚好是在结果中
  • 其实以上1.2.可能是互为补充
    因为我不知道1.中这么组合,好像是可以用01背包组合,就互为帮助
  1. 超递增序列的背包问题可以贪心
    超递增序列:
    ai>si-1的序列是严格超递增序列;
    ai>=si-1的序列是不严格超递增序列;
    如n!,2n,3n
    为什么该背包问题可以贪心?
    因为,从大到小放,
    如果能放下,那一定要,因为不放的话,之后所有的si-1也不会比这个ai更大了
    (刚好能放下,就都yes,忽略一种0!+1!情况不影响,比如2!和0!+1!,n=2)
    所以我们不会有回头路,能放就放,不能放就下一站。
    效率快了一个数量级!

OLE了一次,第一次OLE遇到
即输出超空间,即一直输出
原来有一个坑爹的地方:
The input is terminated by a line with a negative integer.
这不是-1!!!
我除了说认真读题还能说啥。。

几次wa:
1.是要特判的
2.特判就要continue;//不能忘了这个重复输出啊!!

Accepted 660K 63MS:

#include<stdio.h>
//#include<string.h>
//#include<stdlib.h>
//#include<math.h>
#include<iostream>
//#include<algorithm>
//#include<string>
//#include<sstream>
//#include<vector>
//#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
//#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define st first
#define nd second
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
#define N 1000005
int f[15],cnt;
void pre(){
	f[0]=1;
	_forplus(i,1,14){
		f[i]=f[i-1]*i;
	//	cout<<f[i]<<endl;
		if(f[i]>N){
			cnt=i-1;break;
		}
	}
}
int main(){
	fastio
	pre();
	//sort(f,f+cnt+1,greater<int>());排序下面就_forplus,现在弄混了。
	//为了减少复杂度,不排序 
	int n;
	//int c=0;
	while(cin>>n,n>=0){
		//cout<<c++;
		if(n==0){
			cout<<"NO\n";
			continue;//不能忘了这个重复输出啊!! 
		}//特判 /
		_forsub(i,cnt,0){
			if(n>=f[i]){
				n-=f[i];
			}
		} 
		if(n){
			cout<<"NO\n";
		}else{
			cout<<"YES\n";
		}//按这个规则,0是可以被阶乘相加的。确实有0个阶乘相加,但是就不能有阶乘相加,就不太清楚的 
	}
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值