关于贪心算法的定义和如何运用贪心准则来解决一些问题在上一周已经说过了,这周主要来写一下解决了一些什么问题。
老师上课讲到的题
- Students Revenge
有n个命令,要通过p个,某主席要在通过的p个中选择k个接受。每个命令有两个值a,b, a表示如果该主席接受该命令,他的头发变灰的数量,bi表示如果该主席不接受该命令时,议员不高兴值。对于通过的p个命令,该主席要使议员的不高兴值和最小,在相同的情况下,要使自己的头发变灰的数量尽可能的少。让你求出通过哪p个命令,使得该主席的头发变灰的数量最多,在相同的情况下,输出使议员不高兴最大的选择。
问题分析
首先须知道,对于每一个选择(p命令),该主席一定是把b值最小的p-k个不接受,如果b有相同的,则尽可能使自己的头发变灰的数量最小即a值尽量大。step1 :按b:小->大a:大->小排序,前p-k个无论a有多大,如果选它都没用,a不能发挥作用,所以只能在后面的n-(p-k)个里选。
step2 :按a:大->小 b:大->小排序,选择前k个,使得suma最大,b的最小值也最大(因为要保证主席需要接受它)。
step3 :保证了k个命令的suma最大后,剩下的任务就是保证p-k个命令的sumb最大了。再次像step1中那样排序,此时注意在a、b相同时应把已经选在k个中的尽量靠前,然后依次输出后面的命令下标就够了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
using namespace std;
const int N=1e5+5;
struct Node{ int a,b; int id,st; }a[N];
bool cmp1(const Node &x,const Node &y)
{ if(x.b!=y.b) return x.b<y.b;
if(x.a!=y.a) return x.a>y.a;
return x.st<y.st; }
bool cmp2(const Node &x,const Node &y)
{ if(x.a!=y.a) return x.a>y.a; return x.b>y.b; }
bool vis[N];
int main()
{ cin.tie(0);
ios::sync_with_stdio(false);
int n,p,k;
cin>>n>>p>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i].a>>a[i].b;
a[i].id=i;
a[i].st=0;
}
sort(a+1,a+1+n,cmp1); //用第一种方法排序
for(int i=1;i<=p-k;i++) //选出p-k个肯定不会被接受的
a[i].st=1;
sort(a+1,a+1+n,cmp2); //用第二种方法排序
int cnt=0;
for(int i=1;cnt<k;i++) //选出k个肯定会被接受的
if(!a[i].st)
{
cnt++; a[i].st=2;
vis[a[i].id]=true; //用vis记录这k个命令的id
cout<<a[i].id<<" ";
}
sort(a+1,a+1+n,cmp1);
int num=0;
cnt=0;
for(int i=n;num<p-k;i--) //选出p-k个肯定不会被通过的
{//将之前选出的k个找到,在这k个后面选p-k个肯定不会被通过的命令
if(cnt>=k) //即这p-k个的b肯定小于k个的b
{
num++;
cout<<a[i].id<<" ";
}
if(vis[a[i].id]) cnt++;
}
cout<<endl;
return 0;
}
- Gone Finishing去钓鱼,有n条湖按顺序排列,每条湖初始鱼量为fi,钓一个时间片就减少di条鱼,最小减到0条鱼,每条湖间的行走时间为ti个时间片(一个时间片为5分钟),给定时间h小时(一个小时12个时间片),要求最大的钓鱼数。
问题分析:
1.难点在于走路的时间可多可少,应该话多少时间纯钓鱼比较好?解决方案就是——枚举最终停下来的湖,把其分为n中方案,这样每个方案走路时间就确定了,再在每个方案中求最优解,然后优中选优就行了。
2.那么如何在一个方案里找最优解呢?那么就先不要看顺序的找出到停下来的湖为止的湖中最大鱼量的湖。然后有效时间片th内找th次就行了。
3.注意如果有剩余时间,就把剩余时间加到第一条湖的时间上去。
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
using namespace std;
const int N=25;
int n,h; int f[N],d[N],t[N];//f第一个五分钟钓的鱼量,d为每个五分钟减少的鱼量,t为i到i+1五分钟的个数
int ans;int each[N];//记录最终每条湖用的时间
int tans,teach[N];//最优钓鱼量和各湖钓鱼时间
int th,tf[N];//有效钓鱼时间和每条湖前五分钟的钓鱼量
int main() {
int i,j;
while(cin>>n&&n>0)
{ //当湖的数量为0的时候结束
cin>>h;//输入时间
for(i=0; i<n; i++)
{
cin>>f[i];//第一次的鱼量
}
for(i=0; i<n; i++)
{
cin>>d[i];//每五分钟减少的鱼量
}
for(i=0; i<n-1; i++)
{
cin>>t[i];//每个湖间距离需要的时间片
}
h*=12;//一小时12个时间片
ans=-1;
for(i=0; i<n; i++)
{ //表示再第i条湖停下来 //初始化每一次贪心
th=h;//有效时间先初始化为总时间
for(j=0; j<n; j++)
{
tf[j]=f[j];//每条湖初始的钓鱼量初始为第一次五分钟的钓鱼量
teach[j]=0;//每个湖的钓鱼时间初始化为0
}
tans=0;//最大钓鱼数初始化为0 //对每五分钟贪心选择钓鱼量最大的湖钓鱼
while(th>0)
{ //当有效时间大于0
int ind=0,max=tf[0];//令第一条湖的鱼量为最大值 ,ind标记湖是第几条湖
for(j=0; j<=i; j++)
{
if(tf[j]>max) { //不考虑顺序先找第一次鱼量最大的湖
max=tf[j];
ind=j;
}
}
if(max==0)
{ //最大钓鱼量为0时,将剩余的钓鱼时间加到第一个湖上的钓鱼时间
teach[0]+=th*5;//例如样例一
break;
}
else
{
teach[ind]+=5;//最大湖的钓鱼时间,每钓一次加一次五
tans+=tf[ind];//加上最大鱼量的湖的该次的鱼数
if(tf[ind]>=d[ind])//如果鱼量不少于减少的鱼数 ,则减
{
tf[ind]-=d[ind];
}
else
{
tf[ind]=0;//小于减少数则赋值为0
}
}
th--;//有效时间减少一个时间片(一个时间片五分钟)
}
if(i!=n-1)
{ //i的话是表示在第i条湖停下来
h-=t[i];//减去到下一条湖的时间片
}
if(tans>ans)
{ //如果值大于前面的值,就把值赋给ans
ans=tans;
for(j=0; j<n; j++)
{
each[j]=teach[j];//记录最终每条湖用的时间
}
}
}
cout<<each[0];
for(i=1; i<n; i++)
{
cout<<","<<each[i]; }
cout<<endl;
cout<<"Number of fish expected: "<<ans<<endl;
cout<<endl;
}
return 0;
}
OJ上的题
- 最短前缀一个字符串的前缀是从该字符串的第一个字符起始的一个子串。例如 “carbon"的字串是: “c”, “ca”, “car”, “carb”, “carbo”, 和 “carbon”。注意到这里我们不认为空串是字串, 但是每个非空串是它自身的字串. 我们现在希望能用前缀来缩略的表示单词。例如, “carbohydrate” 通常用"carb"来缩略表示. 现在给你一组单词, 要求你找到唯一标识每个单词的最短前缀在下面的例子中,“carbohydrate” 能被缩略成"carboh”, 但是不能被缩略成"carbo" (或其余更短的前缀) 因为已经有一个单词用"carbo"开始一个精确匹配会覆盖一个前缀匹配,例如,前缀"car"精确匹配单词"car". 因此 “car” 是 "car"的缩略语是没有二义性的 , “car”不会被当成"carriage"或者任何在列表中以"car"开始的单词.
问题分析
遍历每个单词可能的前缀 ,并在其他单词从搜索, 如果包含在其他某个单词的前面, 就不能作为前缀 ,搜寻下一个可能的前缀。
如果所有可能的前缀都在其他某个含有就是其本身。
#include<iostream>
#include<cstring>
using namespace std;
string a[1100];
int main()
{ int n=0;
while(cin>>a[n])
n++;
for(int i=0; i<n; i++)
{ int len=a[i].size();
for(int j=0; j<=len; j++)
{
string t=a[i].substr(0,j);//从第0位开始截取j位.
int flag=1;
for(int k=0; k<n; k++)
{
if(i!=k&&t==a[k].substr(0,j))
{
flag=0;
break;
}
}
if(flag||j==len)
{
cout<<a[i]<<" "<<t<<endl;
break;
}
}
}
return 0;
}
- Ride to office
weiwei距离公司4.5千米,骑车去上班,在出发点等待同事,每次骑行都跟最快的同事一起走。
输入 n表示n个同事 每个n有两个值 分别是其速度和出发时间,若时间为负数,则表示在weiwei前面出发 输出最短时间
问题分析
因为他始终跟着最快的同事,所以只需要看在他后面出发的最快的同事到达公司所用的时间即可。
#include <iostream>
#include <cmath>
using namespace std;
const int P=99999999;
int n;double a;
int main()
{ int v,t;
double time;
cin >> n;
while(n!=0)
{ a=P;
for(int i=1; i<=n; i++)
{
cin >> v>>t;
if(t<0)
continue;
time=t+4.5/v*3600;
a=min(time,a);
}
int b=ceil(a);
cout <<b<< endl;
cin >> n;
}
return 0;
}
- 寻找平面上的极大点
在一个平面上,如果有两个点(x,y),(a,b),如果说(x,y)支配了(a,b),这是指x>=a,y>=b;用图形来看就是(a,b)坐落在以(x,y)为右上角的一个无限的区域内。给定n个点的集合,一定存在若干个点,它们不会被集合中的任何一点所支配,这些点叫做极大值点。编程找出所有的极大点,按照x坐标由小到大,输出极大点的坐标。
本题规定:n不超过100,并且不考虑点的坐标为负数的情况。
注意:输出的每个点之间有",“分隔,最后一个点之后没有”,",少输出和多输出都会被判错。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct Node { int x,y;};
int n,k=0;
Node a[110],b[110];
bool cmp(Node c,Node d)
{ return c.x<d.x||(c.x==d.x&&c.y<d.y);}
int main()
{
cin>>n;
for(int i=0; i<n; i++)
{ cin>>a[i].x>>a[i].y; }
sort(a,a+n,cmp);
for(int i=0; i<n; i++)
{ int flag=1;
for(int j=0; j<n; j++)
{ if(i!=j)
{
if(a[i].x<=a[j].x&&a[i].y<=a[j].y)
{
flag=0;
break;
}
}
}
if(flag)
{
b[k].x=a[i].x;
b[k].y=a[i].y;
k++;
}
}
for(int i=0; i<k-1; i++)
{ printf("(%d,%d),",b[i].x,b[i].y); }
printf("(%d,%d)\n",b[k-1].x,b[k-1].y);}
- 电池的寿命
小S新买了一个掌上游戏机,这个游戏机由两节5号电池供电。为了保证能够长时间玩游戏,他买了很多5号电池,这些电池的生产商不同,质量也有差异,因而使用寿命也有所不同,有的能使用5个小时,有的可能就只能使用3个小时。显然如果他只有两个电池一个能用5小时一个能用3小时,那么他只能玩3个小时的游戏,有一个电池剩下的电量无法使用,但是如果他有更多的电池,就可以更加充分地利用它们,比如他有三个电池分别能用3、3、5小时,他可以先使用两节能用3个小时的电池,使用半个小时后再把其中一个换成能使用5个小时的电池,两个半小时后再把剩下的一节电池换成刚才换下的电池(那个电池还能用2.5个小时),这样总共就可以使用5.5个小时,没有一点浪费。现在已知电池的数量和电池能够使用的时间,请你找一种方案使得使用时间尽可能的长。
问题分析
题目可以贪心的考虑最大时长电池,若大于其它所有电池之和,则为其余电池之和,反之为总时长一半。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int main()
{ int n;
double sum2;
while(cin >> n)
{ int sum1=0, maxm= 0, x;
for(int i=1; i<=n; i++)
{
cin >> x;
maxm=max(maxm, x);
sum1+= x;
}
sum2=sum1-maxm;
if(sum2<=maxm)
printf("%.1f\n",sum2);
else
{ double f=sum1/2.0; printf("%.1f\n",f); }
}
return 0;
}
- 拼点游戏
C和S两位同学一起玩拼点游戏。有一堆白色卡牌和一堆蓝色卡牌,每张卡牌上写了一个整数点数。C随机抽取n张白色卡牌,S随机抽取n张蓝色卡牌,他们进行n回合拼点,每次两人各出一张卡牌,点数大者获得三颗巧克力,小者获得一颗巧克力,如果点数相同,每人各得二颗巧克力,使用过的卡牌不得重复使用。已知C和S取到的卡牌点数,请编程计算S最多和最少能得到多少颗巧克力。
问题分析
田忌赛马问题
#include<iostream>
#include<algorithm>
using namespace std;
int n,a[1001],b[1001];
int p(int a[],int b[])
{ int sum=0; int s1=1,l1=n,s2=1,l2=n; while(s1<=l1&&s2<=l2)
{ if(b[l2]>a[l1])
{ sum+=3;
l1--;l2--;
}
else
if(b[s2]>a[s1])
{
sum+=3;
s1++;s2++;
}
else
if(b[s2]==a[l1])
{ sum+=2; s2++;l1--; }
else
{ sum+=1; s2++;l1--; }
}
return sum;
}
int main()
{ int max=0, min=0;
while(cin >> n)
{ if(n==0) break;
for(int i=1;i<=n;i++)
cin >> a[i];
for(int i=1;i<=n;i++)
cin >> b[i];
sort(a+1,a+n+1);
sort(b+1,b+n+1);
max=p(a,b);
min=4*n-p(b,a);
cout <<max<<" "<<min<< endl;
}
return 0;
}
- 最小新整数
给定一个十进制正整数n(0 < n < 1000000000),每个数位上数字均不为0。n的位数为m。现在从m位中删除k位(0<k < m),求生成的新整数最小为多少?例如: n = 9128456, k = 2, 则生成的新整数最小为12456。
问题分析
既然是最小的新整数,那当然要删除掉越靠前并且大于等于它后面的那个数啦,还是用例子来说话吧:
1243865 1怎么删呢?如果你认为是删8,那就错了。如果删8,得124365,但如果删4,得123865,哪个更小呢?毫无疑问是后者吧。那如果是1244444 5呢?最后删到124就删不掉了,所以还有一个条件,如果删了一遍,删不掉,就删去最后一个。大概意思就是这样,由于这道题没有出现有0的情况,所以我在这里暂时不讨论。
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
char a[20];
int main()
{ int t,k;
scanf("%d",&t);
while(t--)
{
scanf("%s%d",a,&k);
int l=strlen(a);
while(k--)
{
for(int i=0; i<l; i++)
{
if(a[i]>a[i+1])
{
for(int j=i; j<l; j++)
{
a[j]=a[j+1];
}
break;
}
}
l--;
}
printf("%s\n",a);
}
return 0;
}
- 特殊密码锁
有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。
然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。当前密码锁状态已知,需要解决的问题是,你至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。
输入两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
问题分析
只需要枚举第一个密码是否按下就可以了。
第一个是否按下会影响第二个密码是否按下,当第一个按下了,第二个密码会随之改变,当第一个不按,第二个密码的状态就不会改变,然后再看第二个密码的是否与目标密码的第二位一致,如果不一致,需要按下第三个按钮,如果一致,就不需要按下第三个按钮,这样循环到末尾,看看是否最后得到的密码与目标密码一致,如果一致,就更新最小值。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
string s1,s2;
int t[404];
bool p(int x)
{ int c=t[x]+t[x-1];
if(c%2==0)
return s1[x]==s2[x];
else
return s1[x]!=s2[x];
}
int f()
{
for(int i=1; i<s1.size(); i++)
{
if(!p(i-1))
{
t[i]=1;
}
}
if(!p(s1.size()-1))
return -1;
int c=0;
for(int i=0; i<s1.size(); i++)
{ c+=t[i]; }
return c;
}
int main()
{
cin>>s1>>s2;
int a=-1;
for(int i=0; i<=1; i++)
{
memset(t,0,sizeof(t));
t[0]=i;
int y=f();
if(y>=0)
{
if(a<0)a=y;
else a=min(a,y);
}
}
if(a==-1)
cout<<"impossible"<<endl;
else
cout<<a<<endl;
return 0;
}
感悟
随着这几周对贪心的学习,已经对其有了一定的自身了解,同时还掌握了一些函数的应用,比如a.substr(i,j) 表示从i的地方开始截取j位还有
memset通常可用来进行清零操作。
今天我们开始学习动态规划的有关问题了,但这并不意味着贪心算法成为了过去式,还是要不断练习巩固!同时要好好学习接下来的内容!!
本文探讨了贪心算法在解决多个实际问题中的应用,包括学生复仇问题、钓鱼问题、最短前缀问题、骑车上班问题、极大点问题、电池寿命优化、拼点游戏策略和特殊密码锁问题。通过问题分析,展示了贪心算法如何通过局部最优解来逼近全局最优解。
3081

被折叠的 条评论
为什么被折叠?



