说说算法题的那些小题儿(C语言版)

来源:互联网 发布:身份证 复制 知乎 编辑:程序博客网 时间:2024/05/18 21:12

本文通过经典的算法题介绍算法设计方法和技巧,探发幽微…

  1. 勇者斗恶龙(The Dragon of Loowater, UVa 11292)
你的王国里有一条n个头的恶龙,你希望雇一些骑士把它杀死(即砍掉所有头)。村里有m个骑士可以雇佣,一个能力值为x的骑士可以砍掉恶龙一个直径不超过x的头,且需要支付x个金币。如何雇佣骑士才能砍掉恶龙的所有头,且需要支付的金币最少?注意,一个骑士只能砍一个头(且不能被雇佣两次)。

【输入格式】
输入包含多组数据。每组数据的第一行为正整数n和m(1≤n,m≤20 000);以下n行每行为一个整数,即恶龙每个头的直径;以下m行每行为一个整数,即每个骑士的能力。输入结束标志为n=m=0。
【输出格式】
对于每组数据,输出最少花费。如果无解,输出“Loowater is doomed!”。
【样例输入】

2 3
5
4
7
8
4
2 1
5
5
10
0 0

【样例输出】

11
Loowater is doomed!
【分析】
能力强的骑士开价高是合理的,但如果被你派去砍一个很弱的头,就是浪费人才了。因此,可以把雇佣来的骑士按照能力从小到大排序,所有头按照直径从小到大排序,一个一个砍就可以了。当然,不能砍掉“当前需要砍的头”的骑士就不要雇佣了。代码如下

#include<cstdio>#include<algorithm>             //因为用到了sortusing namespace std;const int maxn = 20000 + 5;int A[maxn], B[maxn];int main() {  int n, m;  while(scanf("%d%d", &n, &m) == 2 && n && m) {    for(int i = 0; i < n; i++) scanf("%d", &A[i]);    for(int i = 0; i < m; i++) scanf("%d", &B[i]);    sort(A, A+n);    sort(B, B+m);    int cur = 0;                //当前需要砍掉的头的编号    int cost = 0;               //当前总费用    for(int i = 0; i < m; i++)      if(B[i] >= A[cur]) {        cost += B[i];           //雇佣该骑士        if(++cur == n) break;   //如果头已经砍完,及时退出循环      }    if(cur < n) printf("Loowater is doomed!\n");    else printf("%d\n", cost);  }  return 0;}

2.突击战(Commando War, UVa 11729)

你有n个部下,每个部下需要完成一项任务。第i个部下需要你花Bi分钟交待任务,然后他会立刻独立地、无间断地执行Ji分钟后完成任务。你需要选择交待任务的顺序,使得所有任务尽早执行完毕(即最后一个执行完的任务应尽早结束)。注意,不能同时给两个部下交待任务,但部下们可以同时执行他们各自的任务。

【输入格式】
输入包含多组数据,每组数据的第一行为部下的个数N(1≤N≤1 000);以下N行每行两个正整数B和J(1≤B≤10 000,1≤J≤10 000),即交待任务的时间和执行任务的时间。输入结束标志为N=0。
【输出格式】
对于每组数据,输出所有任务完成的最短时间。
【样例输入】

3
2 5
3 2
2 1
3
3 3
4 4
5 5
0

【样例输出】

Case 1: 8
Case 2: 15

【分析】
直觉告诉我们,执行时间较长的任务应该先交待。于是我们想到这样一个贪心算法:按照J从大到小的顺序给各个任务排序,然后依次交待。代码如下。

#include<cstdio>#include<vector>#include<algorithm>using namespace std;struct Job {  int j, b;  bool operator < (const Job& x) const {    //运算符重载。不要忘记const修饰符    return j > x.j;  }};int main() {  int n, b, j, kase = 1;  while(scanf("%d", &n) == 1 && n) {    vector<Job> v;    for(int i = 0; i < n; i++) {      scanf("%d%d", &b, &j); v.push_back((Job){j,b});    }    sort(v.begin(), v.end());               //使用Job类自己的 < 运算符排序    int s = 0;    int ans = 0;    for(int i = 0; i < n; i++) {      s += v[i].b;              //当前任务的开始执行时间      ans = max(ans, s+v[i].j); //更新任务执行完毕时的最晚时间    }    printf("Case %d: %d\n", kase++, ans);  }  return 0;}
上述代码直接交上去就可以通过测试了。可是为什么这样做是对的呢?假设我们交换两个相邻的任务X和Y(交换前X在Y之前,交换后Y在X之前),不难发现其他任务的完成时间没有影响,那么这两个任务呢?情况一:交换之前,任务Y比X先结束,如图(a)所示。不难发现,交换之后X的结束时间延后,Y的结束时间提前,最终答案不会变好。情况二:交换之前,X比Y先结束,因此交换后答案变好的充要条件是:交换后X的结束时间比交换前Y的结束时间早(交换后Y的结束时间肯定变早了),如图(b)所示。这个条件可以写成B[Y]+B[X]+J[X]
#include<cstdio>#include<algorithm>using namespace std;const int maxn = 1000000 + 10;long long A[maxn], C[maxn], tot, M;int main() {  int n;  while(scanf("%d", &n) == 1) { //输入数据大,scanf比cin快     tot = 0;    for(int i = 1; i <= n; i++) { scanf("%lld", &A[i]); tot += A[i]; }            //用%lld输入long long    M = tot / n;    C[0] = 0;     for(int i = 1; i < n; i++) C[i] = C[i-1] + A[i] - M; //递推C数组    sort(C, C+n);    long long x1 = C[n/2], ans = 0; //计算x1    for(int i = 0; i < n; i++) ans += abs(x1 - C[i]);     //把x1代入,计算转手的总金币数    printf("%lld\n", ans);  }  return 0;}

程序本身并没有太多技巧可言,但需要注意的是long long的输入输出。%lld这个占位符并不是跨平台的,比如,Windows下的mingw需要用%I64d而不是%lld。虽然cin/cout没有这个问题,但是本题输入量比较大,cin/cout会很慢。有两个解决方案。一是自己编写输入输出函数(前面已经给过范例),二是使用ios::sync_ with_stdio(false),通过关闭ios和stdio之间的同步来加速,有兴趣的读者可以自行搜索详细信息。
中位数可以在线性时间内求出,但不是本例题的重点(代数分析才是重点),有兴趣的读者可以自行搜索“快速选择”算法的资料。另外,这个程序里的A数组实际上是不必保存的,你能去掉它吗?

0 0
原创粉丝点击