算法错题:贪心

1.均分纸牌问题:
  题目大意:有n堆纸牌排成一行,纸牌总数是n的倍数。每次操作可以从某堆纸牌拿出任意张移动到左边一堆或右边一堆。求最少要多少次操作才能使每堆都是n张。
  思路
  网上的教程好多,但看来看去都没看懂。只有一点点启发。
  首先,如果我们从第i堆移动到第i+1堆,再从第i+1堆拿牌到第i堆显然是很蠢的做法。由于纸牌是排成一行,故最终移动方案必定是像a->b->c f->g h<-j ...这个样子。但是我们怎么知道一堆牌是应该往左移动还是往右移动,对吧?所以到目前为止还是没法做。
  但是,换个思路想一想,如果我们从a堆拿了k张牌到b堆,那么是不是就相当于从b堆,拿了-k张牌到a堆?所以上述最终方案其实就是a->b->c f->g h->j,所有的移动都看成是向左的,那么就可以做了。只需要求出平均数,从左往右扫一遍,如果与平均数不同就把这个差值往下一个推。(推指的是从某一堆拿k(k属于整数)张牌到它右边那堆)
  那么最后可能还会有一点疑惑:为什么按上述操作做出来的一定是我们的最终方案?概括地说叫做:因为方向唯一,答案唯一。
  我们从第一个需要往右推的开始,那么这个第一个需要推的必然就是a,因为左边的已经是平均值了不需要推了。继续,由于a往右拿了不知道多少张牌之后变成了平均值,而这个拿法是唯一的,故我们的第一步a->b和最终方案一定是相同的。
  以此类推,第一步结束之后,b和最终方案中结束了a->b之后的b必定也是相同的。结束a->b->c之后再从f开始,重复上面的证明,可得我们的方案和最终方案是相同的。
上代码:
 1 #include <stdio.h>
 2 int main()
 3 {
 4     long long tot = 0;
 5     long long a[100001] = {0};
 6     int ans = 0, n;
 7     scanf("%d", &n);
 8     for (int i = 0; i < n ; i++) {
 9         scanf("%lld", &a[i]);
10         tot += a[i];
11     }
12     tot /= n;
13     for (int i = 0; i < n - 1; i++) {  
14         a[i] -= tot;
15         if (a[i] != 0) {
16             a[i + 1] += a[i];
17             ans++;
18         }
19     }
20     printf("%d", ans);
21     return 0;
22 }

 

2.相容问题:
  题目大意:给定一些线段,求最大数目的线段,这些线段能放到1~n这个区间且除端点外互不相交。
  思路:简单的来说,就是尽量给后面的线段留出更多的空间。故将线段按照右端点排序。当我们放进某一个线段时,这个线段的右端点的左侧不能再放线段了。
  证明:假设我们已经放了k个线段,第k个线段右端点是r,那么所有左端点小于r的都不能选了。这时我们要放第k + 1个线段,自然是选择右端点最靠左的那个,为什么?
假设右端点最靠左的那个,它的右端点是r1,另一个还没选的线段右端点是r2,r2 > r1,那么选完这一个之后,给后面的线段留下的空间显然更小,那么显然就会导致后面放的总数不会比第k+1个选择r1时更多。
重复上面的证明,可得每次选择右端点最靠左的线段能得到最优解。
上代码:
 1 #include <stdio.h>
 2 struct Node {
 3     int a, b;
 4 };
 5 void sort(struct Node a[], int l, int r)
 6 {
 7     if (l >= r) {
 8         return;
 9     }
10     int i = l - 1;
11     for (int j = l; j <= r; j++) {
12         if (a[j].b <= a[r].b) {
13             struct Node temp = a[j];
14             a[j] = a[++i];
15             a[i] = temp;
16         }
17     }
18     sort(a, l, i - 1);
19     sort(a, i + 1, r);
20 }
21 int main ()
22 {
23     int n;
24     struct Node a[1000004];
25     scanf("%d", &n);
26     for (int i = 0; i < n; i++) {
27         scanf("%d%d", &a[i].a, &a[i].b);
28     }
29     sort(a, 0, n - 1);
30     int ans = 0;
31     int r = 0;
32     for (int i = 0; i < n; i++) {
33         if (a[i].a >= r) {
34             ans++;
35             r = a[i].b;
36         }
37     }
38     printf("%d", ans);
39     return 0;
40 }

 

 

3.字典序最小的名字:
  题目大意
  给定一堆名字,将他们首尾相连,求字典序最小的连接方法。
  思路以及证明
  其实还是排序,但排序方式有所不同。
  首先我们知道,反正最后所有名字都要排进去,区别只是先后问题,这点是母庸质疑的。那么问题就来了,如何知道某两个字符串a和b,a先排进去比b先排进去更优呢?
  我们首先考虑他俩排在一起的情况,那么显然只要比较ab和ba哪个字典序更小(暂且写作ab<ba)就行了。如果他俩不排在一起呢?比如acb和bca呢?
  那么沿着上面的思路继续走:如果我们就以ab<ba然后将a安排在b之前(不知道是之前多少,也就是快排的思路),那么假设最后排完序得到了字符串acb,即a和b之间夹了某个字符串c,那么按照我们的排序方式必然有ac<ca,cb<bc,在之前得到了ab<ba,那么由上述三个式子得到:acb<cab, bac<bca,cba<bca,acb<abc,abc<bac,cab<cba,由这6个式子推出 acb是所有组合中的最优解。
上代码:
 1 #include <stdio.h>
 2 #include <string.h>
 3 struct Node {
 4     char name[101];
 5 };
 6 int stcmp (char a[], char b[]) 
 7 {
 8     char c[101] = {0}, d[101] = {0};
 9     strcat(c, a);
10     strcat(c, b);
11     strcat(d, b);
12     strcat(d, a);
13     return strcmp(c,d);
14 }
15 void sort(struct Node a[], int l, int r)
16 {
17     if (l >= r) {
18         return;
19     }
20     int i = l - 1;
21     for (int j = l; j <= r; j++) {
22         if (stcmp(a[j].name, a[r].name) <= 0) {
23             i++;
24             struct Node temp = a[i];
25             a[i] = a[j];
26             a[j] = temp;
27         }
28     }
29     sort(a, l, i - 1);
30     sort(a, i + 1, r);
31     return;
32 }
33 int main()
34 {
35     int n;
36     struct Node a[101];
37     scanf("%d", &n);
38     for (int i = 0; i < n; i++) {
39         scanf("%s", a[i].name);
40     }
41     sort(a, 0, n - 1);
42     for (int i = 0; i < n; i++) {
43         printf("%s", a[i].name);
44     }
45     return 0;
46 }

 

 
posted @ 2020-06-29 21:04  工程1  阅读(405)  评论(0)    收藏  举报