众所周知,贪心是一个比较恶心的算法。
区别于常规的算法或数据结构,贪心题一般不会让人看到就想到思路,而是需要我们在一些猜测或是感性分析下,找到一种局部最优的方案,并且可以通过局部最优解推出全局最优解。
T1:防晒
有C头奶牛进行日光浴,第i头奶牛需要minSPF[i]到maxSPF[i]单位强度之间的阳光。
每头奶牛在日光浴前必须涂防晒霜,防晒霜有L种,涂上第i种之后,身体接收到的阳光强度就会稳定为SPF[i],第i种防晒霜有cover[i]瓶。
求最多可以满足多少头奶牛进行日光浴。
输入格式
第一行输入整数C和L。
接下来的C行,按次序每行输入一头牛的minSPF和maxSPF值,即第i行输入minSPF[i]和maxSPF[i]。
再接下来的L行,按次序每行输入一种防晒霜的SPF和cover值,即第i行输入SPF[i]和cover[i]。
每行的数据之间用空格隔开。
输出格式
输出一个整数,代表最多可以满足奶牛日光浴的奶牛数目。
数据范围
1≤C,L≤25001≤C,L≤2500,
1≤minSPF≤maxSPF≤10001≤minSPF≤maxSPF≤1000,
1≤SPF≤10001≤SPF≤1000
输入样例:
3 2
3 10
2 5
1 5
6 2
4 1
输出样例:
2
不难想到,该题的贪心策略为:
1.将所有奶牛的maxSPF从大到小排序。
2.枚举每一头奶牛,选择可选防晒霜中SPF值最大的一个使用。
证明:
1.如果把所有奶牛与防晒霜的SPF值都看成点,那么这个题实际上就是一个二分图的最大匹配问题。
2.根据匈牙利算法,如果已匹配的二分图中不存在增广路,那么这就是一个最大匹配的二分图。
3.如果以我们的算法不可以找到最短增广路,那么此算法即为正解4.如果以一个防晒霜a的SPF值为起点,那么根据算法可以看出,下一个增广路径中的防晒霜的SPF值b一定在a所匹配的奶牛的SPF区间的右侧。
5.那么就可以根据增广路径的最短性得知增广路径只能从奶牛maxSPF由小到大去得到。
6.如果我们找到了最短增广路,那么此时具有最大maxSPF的奶牛没有点匹配,而具有第二大maxSPF的奶牛有点匹配,与算法矛盾。7.该算法正确。
代码:
#include#include #include using namespace std; const int N=2510; typedef pair<int,int> PII; PII cow[N],cover[N]; int C,L; int main() { cin>>C>>L; for (int i=0;i i) cin>>cow[i].first>>cow[i].second; for (int i=0;i i) cin>>cover[i].first>>cover[i].second; sort(cow,cow+C); int ans=0; for (int i=C-1;i>=0;--i) { int max_cover=0,p; for (int j=0;j j) if (cover[j].first>=cow[i].first&&cover[j].first<=cow[i].second&&max_cover<cover[j].first) max_cover=cover[j].first,p=j; if (max_cover!=0) { ans++; cover[p].second--; if (cover[p].second==0) cover[p].first=0; } } cout<<ans; return 0; }
T2.畜栏预定
有N头牛在畜栏中吃草。
每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏。
给定N头牛和每头牛开始吃草的时间A以及结束吃草的时间B,每头牛在[A,B]这一时间段内都会一直吃草。
当两头牛的吃草区间存在交集时(包括端点),这两头牛不能被安排在同一个畜栏吃草。
求需要的最小畜栏数目和每头牛对应的畜栏方案。
输入格式
第1行:输入一个整数N。
第2..N+1行:第i+1行输入第i头牛的开始吃草时间A以及结束吃草时间B,数之间用空格隔开。
输出格式
第1行:输入一个整数,代表所需最小畜栏数。
第2..N+1行:第i+1行输入第i头牛被安排到的畜栏编号,编号从1开始,只要方案合法即可。
数据范围
1≤N≤500001≤N≤50000,
1≤A,B≤10000001≤A,B≤1000000
输入样例:
5
1 10
2 4
3 6
5 8
4 7
输出样例:
4
1
2
3
2
4
更不难想到,此题的贪心做法:
1.将每头牛吃草的起始时间A排序。
2.枚举每一头牛,若此时所有畜栏的最小吃草末尾时间<该牛的起始时间,就另增一个畜栏,并保存末尾时间。
证明:
1.根据此算法得出的方案中的所有畜栏不存在任意两个之间没有交集。
2.所以最优解>=求得的方案。
3.又因为最优解<=求得的方案。
4.该算法正确。
代码:
#include#include #include #include #include using namespace std; const int N=50010; typedef pair<int,int> PII; pair int> cow[N]; int n,mark[N]; int main() { cin>>n; for (int i=0;i i) { cin>>cow[i].first.first>>cow[i].first.second; cow[i].second=i; } sort(cow,cow+n); priority_queue< PII,vector ,greater > heap; for (int i=0;i i) if (heap.empty()||cow[i].first.first<=heap.top().first) { PII stall=make_pair(cow[i].first.second,heap.size()+1); heap.push(stall); mark[cow[i].second]=stall.second; } else { mark[cow[i].second]=heap.top().second; PII stall=make_pair(cow[i].first.second,heap.top().second); heap.pop(); heap.push(stall); } cout< endl; for (int i=0;i endl; return 0; }
T3.雷达设备
假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。
每个小岛都位于海洋一侧的某个点上。
雷达装置均位于海岸线上,且雷达的监测范围为d,当小岛与某雷达的距离不超过d时,该小岛可以被雷达覆盖。
我们使用笛卡尔坐标系,定义海岸线为x轴,海的一侧在x轴上方,陆地一侧在x轴下方。
现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。
输入格式
第一行输入两个整数n和d,分别代表小岛数目和雷达检测范围。
接下来n行,每行输入两个整数,分别代表小岛的x,y轴坐标。
同一行数据之间用空格隔开。
输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出“-1”。
数据范围
1≤n≤10001≤n≤1000
输入样例:
3 2
1 2
-3 1
2 1
输出样例:
2
算法:
把所有岛屿的x轴上可监测区间求出。
把所有区间的末端点排序。
枚举每一个区间,若第i*区间初端点被第i区间末端点包含,方案数+1,并保存新的末端点。
证明:
该算法得出的每个方案中的首区间两两不存在交集,因此得证。
代码:
#include#include #include #include using namespace std; typedef pair<double,double> PII; const int N=1010; PII len[N]; bool cmp(PII x,PII y) { return x.second<y.second; } int main() { int n; double d; cin>>n>>d; for (int i=0;i i) { cin>>len[i].first>>len[i].second; int x=len[i].first,y=len[i].second; if (y>d) {cout<<-1; return 0;} len[i]=make_pair(x-sqrt(d*d-y*y),x+sqrt(d*d-y*y)); } sort(len,len+n,cmp); int ans=1; double p=len[0].second; for (int i=1;i i) if (len[i].first>p) ++ans,p=len[i].second; cout<<ans; }
T4.国王游戏
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。
输入格式
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
数据范围
输入样例:
3
1 1
2 3
7 4
4 6
输出样例:
2
思路:
考虑每一位大臣(用i表示),通过列式子可得,如果i前面的一位大臣i-1左右手的乘积>第i位大臣,那么这两位大臣就可以交换位置,结果会更优。
那么假设最优排列下获奖最多的大臣在第x个位置,容易得到:
(不包括国王)第1~第x-1位大臣左右手乘积都小于第x位大臣
第x+1~第n位大臣左右手乘积都大于第x位大臣
所以我们不妨对所有大臣以左右手的乘积为关键字排序,这样一来,最优解的位置也就确定了。
对了,这道题还要写高精度,个人认为这才是这道题最恶心的地方。。。。
代码:
#include#include #include #include using namespace std; const int N=1010; typedef pair<int,int> PII; PII member[N],sum[N]; int n; vector<int> mul(vector<int> a,int b) { vector<int> c; int t=0; for (int i=a.size()-1;i>=0;--i) { t+=a[i]*b; c.push_back(t%10); t/=10; } while (t) { c.push_back(t%10); t/=10; } reverse(c.begin(),c.end()); return c; } vector<int> div(vector<int> a,int b) { vector<int> c; int t=0,s=0; bool flag=false; for (int i=0;i i) { t=t*10+a[i]; s=t/b; if (flag||s) { flag=true; c.push_back(s); t%=b; } } return c; } vector<int> get_max(vector<int> a,vector<int> b) { if (a.size()>b.size()) return a; if (a.size() return b; if (a>b) return a; return b; } void output(vector<int> a) { for (int i=0;i i) cout<<a[i]; return; } int main() { cin>>n; int x,y; for (int i=0;i<=n;++i) { cin>>x>>y; member[i]=make_pair(x,y); sum[i].first=x*y; sum[i].second=x; } sort(sum+1,sum+n+1); vector<int> res; res.push_back(1); vector<int> sum_max; sum_max.push_back(0); for (int i=0;i<=n;++i) { if (i) sum_max=get_max(sum_max,div(res,sum[i].first/sum[i].second)); res=mul(res,sum[i].second); } output(sum_max); return 0; }
T5.给树染色
一颗树有 n 个节点,这些节点被标号为:1,2,3…n,每个节点 i 都有一个权值 A[i]。
现在要把这棵树的节点全部染色,染色的规则是:
根节点R可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。
每次染色的代价为T*A[i],其中T代表当前是第几次染色。
求把这棵树染色的最小总代价。
输入格式
第一行包含两个整数 n 和 R ,分别代表树的节点数以及根节点的序号。
第二行包含 n 个整数,代表所有节点的权值,第 i 个数即为第 i 个节点的权值 A[i]。
接下来n-1行,每行包含两个整数 a 和 b ,代表两个节点的序号,两节点满足关系: a 节点是 b 节点的父节点。
除根节点外的其他 n-1 个节点的父节点和它们本身会在这 n-1 行中表示出来。
同一行内的数用空格隔开。
输出格式
输出一个整数,代表把这棵树染色的最小总代价。
数据范围
1≤n≤10001≤n≤1000,
1≤A[i]≤10001≤A[i]≤1000
输入样例:
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
0 0
输出样例:
33
思路:
首先,我们知道,如果这道题没有“在被染色之前它的父亲节点必须已经染上了色”这句话的限制,我们会按权值从大到小的顺序染色。
现在加上了这句话的限制,但从全局考虑,我们还是必须尽量优先考虑权值较大的点。
所以,对于一个权值最大的点,我们可以认为,一旦它的父节点被染色,我们立即就要将此节点染色。换句话说,这两个点一定是连在一起考虑的。
另外,可以通过列式子得知,若有两个已经确定先后染色顺序的点集,判断这两个点集谁先谁后染色与它们的平均权值有关系。那么,我们就可以将这两个点等效地看作一个点,并将这两个点的平均权值替换父节点的权值,并删除子节点。这对我们对问题的处理没有任何影响。
所以,我们只要每次找出整个树中最大的那个点,并与其父节点合并成一个新的节点,以此类推,就可以得到最优解。
代码:
#include#include using namespace std; typedef long long LL; const int N=1010; struct Node{ int sum,father,size; double ave; }leaf[N]; LL ans; int n,R; int get_max() { int p=0; double leaf_max=0; for (int i=1;i<=n;++i) if ((leaf[i].ave>leaf_max)&&(i!=R)) leaf_max=leaf[i].ave,p=i; return p; } int main() { cin>>n>>R; int x=0,y=0; for (int i=1;i<=n;++i) { cin>>x; leaf[i].sum=x; leaf[i].size=1; leaf[i].ave=x; ans+=leaf[i].sum; } for (int i=1;i i) { cin>>x>>y; leaf[y].father=x; } cin>>x>>y; for (int i=1;i i) { int p=get_max(); int fa=leaf[p].father; ans+=leaf[p].sum*leaf[fa].size; leaf[fa].sum+=leaf[p].sum; leaf[fa].size+=leaf[p].size; leaf[fa].ave=(double)leaf[fa].sum/leaf[fa].size; for (int j=1;j<=n;++j) if (leaf[j].father==p) leaf[j].father=leaf[p].father; leaf[p].ave=-1; } cout<<ans; }
T6.最大的和
给定一个包含整数的二维矩阵,子矩形是位于整个阵列内的任何大小为1 * 1或更大的连续子阵列。
矩形的总和是该矩形中所有元素的总和。
在这个问题中,具有最大和的子矩形被称为最大子矩形。
例如,下列数组:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
其最大子矩形为:
9 2
-4 1
-1 8
它拥有最大和15。
输入格式
输入中将包含一个N*N的整数数组。
第一行只输入一个整数N,表示方形二维数组的大小。
从第二行开始,输入由空格和换行符隔开的N2N2个整数,它们即为二维数组中的N2N2个元素,输入顺序从二维数组的第一行开始向下逐行输入,同一行数据从左向右逐个输入。
数组中的数字会保持在[-127,127]的范围内。
输出格式
输出一个整数,代表最大子矩形的总和。
数据范围
1≤N≤1001≤N≤100
输入样例:
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
输出样例:
15
思路:
首先这道题有一个暴力的解法,枚举每一个矩形的左上角坐标和右下角坐标,通过前缀和来求出最大的矩阵和,时间复杂度为O(n^4),似乎有点不太能接受的样子。。。
所以我们可以只枚举矩阵的纵向宽度,那么把每一列的和加起来就转化为了经典的一维的最大子序列和的问题了。时间复杂度为O(n^3)。
代码:
#include#include using namespace std; const int SIZE=110; int a[SIZE][SIZE],s[SIZE][SIZE],n; int main() { cin>>n; for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) { cin>>a[i][j]; s[i][j]=a[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1]; } int ans=-9999999,p=0; for (int j=1;j<=n;++j) for (int i=1;i<=j;++i) { p=0; for (int k=1;k<=n;++k) { int q=s[j][k]+s[i-1][k-1]-s[i-1][k]-s[j][k-1]; if (p+q>=0) ans=max(ans,p+q),p+=q; else ans=max(ans,p+q),p=0; } } cout<<ans; return 0; }
T7.任务
今天某公司有M个任务需要完成。
每个任务都有相应的难度级别和完成任务所需时间。
第i个任务的难度级别为yiyi,完成任务所需时间为xixi分钟。
如果公司完成此任务,他们将获得(500 * xi + 2 * yi)美元收入。
该公司有N台机器,每台机器都有最长工作时间和级别。
如果任务所需时间超过机器的最长工作时间,则机器无法完成此任务。
如果任务难度级别超过机器的级别,则机器无法完成次任务。
每台机器一天内只能完成一项任务。
每个任务只能由一台机器完成。
请为他们设计一个任务分配方案,使得该公司能够最大化他们今天可以完成的任务数量。
如果有多种解决方案,他们希望选取赚取利润最高的那种。
输入格式
输入包含几个测试用例。
对于每个测试用例,第一行包含两个整数N和M,分别代表机器数量和任务数量。
接下来N行,每行包含两个整数xi,yi,分别代表机器最长工作时间和机器级别。
再接下来M行,每行包含两个整数xi,yi,分别代表完成任务所需时间和任务难度级别。
输出格式
对于每个测试用例,输出两个整数,代表公司今天可以完成的最大任务数以及他们将获得的收入。
数据范围
1≤N,M≤100000,
0
输入样例:
1 2
100 3
100 2
100 1
输出样例:
1 50004
毒瘤题!!太他娘恶心了。。。。
不看数据范围根本想不出来怎么贪心。。。
数据范围表明:即使在最坏情况下,xi=1,yi=100,此时500*1依然大于100*2,所以,所谓“利润最大”实际上也是“工作时间尽量长”。。。
所以,,,排序就完事了。。。
代码:
#include#include #include #include<set> using namespace std; const int SIZE=100010; typedef long long LL; typedef pair<int,int> PII; PII mach[SIZE],task[SIZE]; int n,m; LL ans,cnt; int main() { while (cin>>n>>m) { for (int i=0;i >mach[i].first>>mach[i].second; for (int i=0;i >task[i].first>>task[i].second; sort(mach,mach+n); sort(task,task+m); multiset<int> ys; for (int i=m-1,j=n-1;i>=0;--i) { while (j>=0&&mach[j].first>=task[i].first) ys.insert(mach[j--].second); auto p=ys.lower_bound(task[i].second); if (p!=ys.end()) { cnt++; ans+=task[i].first*500+task[i].second*2; ys.erase(p); } } cout< " "< endl; } return 0; }