算法错题:贪心
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 }

浙公网安备 33010602011771号