C语言应用实例:硕鼠游戏,田忌赛马,搬桌子,活动选择(贪心算法)
一、硕鼠游戏
1.题目
小老鼠准备了 M 磅的猫粮,准备去和看守仓库的猫做交易,因为仓库里有小老鼠喜欢吃的五香豆。
仓库有 N 个房间;
第 i 个房间有 J[i] 磅的五香豆,并且需要用 F[i] 磅的猫粮去交换;
老鼠不必交换该房间所有的五香豆,换句话说,它可以用 F[i]∗a% 磅的猫粮去换取 J[i]∗a% 磅的五香豆,其中 a 是一个实数。
现在,请帮忙计算一下,小老鼠最多能够得到多少磅的五香豆?
要求:
输入包含多组测试用例。
每组测试数据首先一行是 2 个非负整数 M 和 N ,接着的 N 行,每行分别包含 2 个非负整数 J[i] 和 F[i] 。
输入数据以两个 −1 结束。
题目保证所有的数据不超过 1000
请计算并输出小老鼠最多能够得到的五香豆数量。
每组数据输出一行,保留 3 位小数。
2.题解
这是一个典型的贪心算法题
即不考虑全局,优先考虑局部最优解(全局是否最优需要证明)
代入题目即寻找能够获得最多猫粮的方案
有了思路,接下来便到了写代码的时候
读完要求我们会发现,每组数据有N个房间,每个房间至少包含两个数据(五香豆和猫粮),很明显,房间可以用一个结构体数组来表示,而数据则可以纳入对应结构体
结构体代码如下
struct mou{int beans;//存储房间内的豆子数量int mon;//存储需要缴纳的猫粮double mid;//计算单位数量的猫粮可兑换的豆子数量
};核心算法如下
while(M){if(i<0){break;}if(M>=moulist[i].mon){sum+=moulist[i].beans;M-=moulist[i].mon;}else{sum+=moulist[i].mid*M;M=0;}i--;}注:计算前先将结构体数组(moulist)按照mid大小排序
3.完整代码
#include<stdio.h>
#include<stdlib.h>struct mou{int beans;int mon;double mid;
};int compare(const void *a,const void *b){struct mou *m1 = (struct mou*)a;struct mou *m2 = (struct mou*)b;if(m1->mid > m2->mid) return 1;if(m1->mid < m2->mid) return -1;return 0;
}int main(){int M,N;while(1){scanf("%d %d",&M,&N);if(M==-1&&N==-1){break;}struct mou *moulist=(struct mou*)malloc(sizeof(struct mou)*N);for(int i=0;i<N;i++){struct mou m;scanf("%d %d",&m.beans,&m.mon);m.mid=(double)m.beans/m.mon;moulist[i]=m;}qsort(moulist,N,sizeof(struct mou),compare);double sum=0;int i=N-1;while(M){if(i<0){break;}if(M>=moulist[i].mon){sum+=moulist[i].beans;M-=moulist[i].mon;}else{sum+=moulist[i].mid*M;M=0;}i--;}printf("%.3lf\n", sum);free(moulist);}return 0;
}二、田忌赛马
1.题目
“田忌赛马” 是中国历史上一个著名的故事。
大约2300年前,齐国大将田忌喜欢和国王赛马,并且约定:每赢一场,对方就要付 200 元。
假设已知田忌和国王的各自马匹的速度都不相同,请计算田忌最好的结果是什么。
要求:输入包含多组测试样例。
每组样例的第一行是一个整数 n(n<=1000),表示田忌和国王各自参赛的马匹数量。
接下来一行的 n 个整数表示田忌的马的速度,再接下来一行的 n个整数表示国王的马的速度。
n 为 0 时,表示输入数据的结束。
每组数据输出一行,表示田忌最多能够赢得的金额。
2.题解
这道题有两种情况:一种是田忌的马比国王的好,此时直接比就行;另一种是田忌的马比国王的差,此时则需要贪心算法
我们可以定义两个数组(t(田忌)和g(国王))用于存储马的质量
然后将二者分别排序便于后续操作
int compare(const void *a,const void *b){return (*(int*)a-*(int*)b);
}
int main(){
int t[n+1];int g[n+1];for(int i=0;i<n;i++){scanf("%d",&t[i]);}for(int i=0;i<n;i++){scanf("%d",&g[i]);}qsort(t,n,sizeof(int),compare);qsort(g,n,sizeof(int),compare);
}核心算法如下
int l=0;int r=n-1;int sum=0;for(int i=n-1;i>=0;i--){if(g[i]<t[r]){r--;sum++;}else if(g[i]>t[r]){l++;sum--;}}3.完整代码
#include<stdio.h>
#include<stdlib.h>int compare(const void *a,const void *b){return (*(int*)a-*(int*)b);
}int main(){int n;while(1){scanf("%d",&n);if(n==0){break;}int t[n+1];int g[n+1];for(int i=0;i<n;i++){scanf("%d",&t[i]);}for(int i=0;i<n;i++){scanf("%d",&g[i]);}qsort(t,n,sizeof(int),compare);qsort(g,n,sizeof(int),compare);int l=0;int r=n-1;int sum=0;for(int i=n-1;i>=0;i--){if(g[i]<t[r]){r--;sum++;}else if(g[i]>t[r]){l++;sum--;}}printf("%d\n",sum*200);}return 0;
}三、搬桌子
1.题目
已知楼层房间情况如下:

楼层中间是走廊,两侧各有 200 个房间,编号如图。
由于内部调整,需要把一些桌子从一个房间搬到另外的房间。
因为走廊很窄,但是桌子很大,所以同一段走廊每次只能通过一个桌子。
假设不论远近,每趟搬桌子都需要 10 分钟。
同时,当你从 房间i 搬桌子到 房间j 的过程中,房间i 到 房间j 之间的走廊都被占用,也就是说,在每个10分钟内,不能有多个任务共享同一段走廊。
现在,要完成所有的搬运任务,最少需要多少时间?
要求:输入包含 T 组测试用例。
每组测试用例首先是一个正整数 N(1<=N<=200),表示需要搬运的桌子数量。
接下来 N 行,每行包含 2 个正整数 s 和 t,表示需要将一个桌子从 房间s 搬到 房间t。
计算并输出完成所有的搬运任务需要的最少的时间,每组数据占一行。
2.题解
由题可知,中间一段走廊可以分为200段
那么如何找出总搬运时间呢?
其实很简单,我们只需用一个大小为201的数组来表示走廊,并初始化为0,然后每搬运一次就让其加一,最后找出最大重叠数乘10即可
(注意避开活动选择思想误区:即优先选择最早结束且满足要求的区间)
3.完整代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(){int T;scanf("%d",&T);for(int i=0;i<T;i++){int N;scanf("%d",&N);int corridor[201] = {0}; // 走廊段,索引1-200for(int j=0;j<N;j++){int s, t;scanf("%d %d",&s,&t);// 确保s <= tif(s > t){int temp = s;s = t;t = temp;}// 将房间号映射到走廊段// 房间1-2 → 走廊段1,房间3-4 → 走廊段2,以此类推int start = (s + 1) / 2;int end = (t + 1) / 2;// 标记被占用的走廊段for(int k=start; k<=end; k++){corridor[k]++;}}// 找出最大重叠数int max_overlap = 0;for(int j=1; j<=200; j++){if(corridor[j] > max_overlap){max_overlap = corridor[j];}}printf("%d\n", max_overlap * 10);}return 0;
}4.错误写法(活动选择思想)
这里再贴一下主包一开始的错误写法(还超时了)
(这并不贪心,而是另一种并排的思想)
#include<stdio.h>
#include<math.h>
#include<stdlib.h>struct Rom{int s,t;int num;
};int compare(const void *a,const void *b){struct Rom *m1 = (struct Rom*)a;struct Rom *m2 = (struct Rom*)b;if(m1->t > m2->t) return 1;if(m1->t < m2->t) return -1;return 0;
}int main(){int T;scanf("%d",&T);for(int i=0;i<T;i++){int N;scanf("%d",&N);struct Rom *romlist=(struct Rom*)malloc(sizeof(struct Rom)*N);for(int j=0;j<N;j++){struct Rom ro;scanf("%d %d",&ro.s,&ro.t);ro.num=1;romlist[j]=ro;}qsort(romlist,N,sizeof(struct Rom),compare);int a=N;int sum=0;int min=romlist[0].t;while(a){for(int j=0;j<N;j++){if(romlist[j].s>=min&&romlist[j].num!=0){sum++;romlist[j].num=0;a--;min=romlist[j].t;}}}printf("%d",sum*10);}return 0;
}当笔记写了/doge
四、活动选择
1.题目
“今年暑假不AC?”
“是的。”
“那你干什么呢?”
“看世界杯呀,笨蛋!”
“@#$%^&*%…”
确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视了。
作为球迷,一定想看尽量多的完整的比赛,当然,作为新时代的好青年,你一定还会看一些其它的节目,比如新闻联播(永远不要忘记关心国家大事)、非常6+7、超级女生,以及王小丫的《开心辞典》等等,假设你已经知道了所有你喜欢看的电视节目的转播时间表,你会合理安排吗?(目标是能看尽量多的完整节目)
要求:输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Tis,Tie (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。
对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。
2.题解
这是典型的活动选择题目
先定义节目结构体
struct Rom{int s,t;
};然后定义节目结构体数组
struct Rom *romlist=(struct Rom*)malloc(sizeof(struct Rom)*N);并将其按照结束时间排序
qsort(romlist,N,sizeof(struct Rom),compare);贪心算法
int count = 1; // 至少可以选择第一个活动int last_end = romlist[0].t;for(int j=1;j<N;j++){if(romlist[j].s >= last_end){count++;last_end = romlist[j].t;}}最后释放内存
3.完整代码
#include<stdio.h>
#include<stdlib.h>struct Rom{int s,t;
};int compare(const void *a,const void *b){struct Rom *m1 = (struct Rom*)a;struct Rom *m2 = (struct Rom*)b;if(m1->t > m2->t) return 1;if(m1->t < m2->t) return -1;return 0;
}int main(){int N;while(1){scanf("%d",&N);if(N==0){break;}struct Rom *romlist=(struct Rom*)malloc(sizeof(struct Rom)*N);for(int j=0;j<N;j++){scanf("%d %d",&romlist[j].s,&romlist[j].t);}qsort(romlist,N,sizeof(struct Rom),compare);int count = 1; int last_end = romlist[0].t;for(int j=1;j<N;j++){if(romlist[j].s >= last_end){count++;last_end = romlist[j].t;}}printf("%d\n",count);free(romlist);}return 0;
}