第四篇 枚举思想
一: 思想
有时我们解决某个问题时找不到一点规律,此时我们很迷茫,很痛苦,很蛋疼,突然我们灵光一现,发现候选答案的问题规模在百万之内,
此时我们就想到了从候选答案中逐一比较,一直找到正确解为止。
二: 条件
前面也说了,枚举是我们在无奈之后的最后一击,那么使用枚举时我们应该尽量遵守下面的两个条件。
① 地球人都不能给我找出此问题的潜在规律。
② 候选答案的集合是一个计算机必须能够承受的。
三:举例
下面是一个填写数字的模板,其中每个字都代表数字中的”0~9“,那么要求我们输入的数字能够满足此模板。
思路1):对“算”“法”“洗”“脑”“题”分别进行枚举,复杂度达到了O(n5)。
//“算”字的取值范围 for (int i1 = 1; i1 < 10; i1++) { //“法”字的取值范围 for (int i2 = 0; i2 < 10; i2++) { //“洗”字的取值范围 for (int i3 = 0; i3 < 10; i3++) { //"脑"字的取值范围 for (int i4 = 0; i4 < 10; i4++) { //"题"字的取值范围 for (int i5 = 1; i5 < 10; i5++) { count++; //一个猜想值 var guess = (i1 * 10000 + i2 * 1000 + i3 * 100 + i4 * 10 + i5) * i1; //最终结果值 var result = i5 * 100000 + i5 * 10000 + i5 * 1000 + i5 * 100 + i5 * 10 + i5; if (guess == result)
思路2):对乘积“算算算算算算”和被乘数“算”进行枚举,复杂度达到了O(n2)。
//商 int[] resultArr = { 111111, 222222, 333333, 444444, 555555, 666666, 777777, 888888, 999999 }; //除数 int[] numArr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int count = 0; for (int i = 0; i < resultArr.Count(); i++) { for (int j = 0; j < numArr.Count(); j++) { count++; var result = resultArr[i].ToString(); var num = numArr[j].ToString(); var origin = (resultArr[i] / numArr[j]).ToString(); if (origin.LastOrDefault() == result.FirstOrDefault() && origin.FirstOrDefault() == num.FirstOrDefault() && result.Length - 1 == origin.Length)
再举一例:五家共井
古代数学巨著《九章算数》中有这么一道题叫“五家共井,甲二绠(汲水用的井绳)不足,如(接上)乙一绠;乙三绠不足,如丙一绠;
丙四绠不足,如丁一绠;丁五绠不足,如戊一绠;戊六绠不足,如甲一绠,皆及。
意思就是说五家人共用一口井,甲家的绳子用两条不够,还要再用乙家的绳子一条才能打到井水;乙家的绳子用三条不够,还要再用丙家的绳子
一条才能打到井水;丙家的绳子用四条不够,还要再用丁家的绳子一条才能打到井水;丁家的绳子用五条不够,还要再用戊家的绳子一条才能打
到井水;戊家的绳子用六条不够,还要再用甲家的绳子一条才能打到井水。
最后问:井有多深?每家的绳子各有多长?
分析:同样这套题也是属于不定方程,拿这个题目的目地就是让大家能够在不定方程组这种范畴问题上做到“举一反三”,根据题意
我们设井深为h,各家分别为a,b,c,d,e,则可以列出如下方程组:
2a+b=h ①
3b+c=h ②
4c+d=h ③
5d+e=h ④
6e+a=h ⑤
首先我们看下普通青年的想法,他们的想法是找a,b,c,d,e之间的对应关系。
依次将②代入①,③代入②,④代入③,⑤代入④可得如下方程组:
a=b+c/2
b=c+d/3
c=d+e/4
d=e+a/5
从计算机的角度来说,我不希望有小数的出现,所以我可推断: c一定是2的倍数,d一定是3的倍数,e一定是4的倍数,a一定是5的倍数,根据这种关系我们
就可以有如下代码:
1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 int a, b, c, d, e, h;
8
9 a = b = c = d = e = h = 0;
10
11 bool flag = true;
12
13 while (flag)
14 {
15 //4的倍数
16 e += 4;
17
18 a = 0;
19
20 while (flag)
21 {
22 //5的倍数
23 a += 5;
24
25 d = e + a / 5;
26
27 c = d + e / 4;
28
29 if (c % 2 != 0)
30 continue;
31
32 if (d % 3 != 0)
33 continue;
34
35 b = c + d / 3;
36
37 if (b + c / 2 < a)
38 break;
39
40 if (b + c / 2 == a)
41 flag = false;
42 }
43 }
44
45 h = 2 * a + b;
46
47 Console.WriteLine("a={0},b={1},c={2},d={3},e={4} ------h={5}\n", a, b, c, d, e, h);
48
49 Console.Read();
50 }
51 }
52 }
同样我们的时间复杂度是O(N2),急需优化。
我们再来看看文艺青年的想法,他们的想法是找a,b,c,d,e中的某个数与h的对应关系。
比如我就找c与h的对应关系,上面的①②③④⑤可写成如下方程组:
b=h-2a ⑥
c=h-3b ⑦
d=h-4c ⑧
e=h-5d ⑨
a=h-6e ⑩
将⑥,⑧,⑨,⑩分别代入⑦,一阵痉挛后可知:
c=(148/721)h
上面的公式也就表明了c和h的比例关系,我们令 h=721k,则 c=148k,将其代入⑥,⑦,⑧,⑨,⑩可得如下方程组
a=265k
b=191k
c=148k
d=129k
e=76k
x=721k
又因为k>0,所以题目有无数个解。这里我就取0<k<5,否则绳子已经到达极限了,需要用蛟龙号去深潜了。
1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 for (int k = 1; k < 5; k++)
8 {
9 int h = 721 * k;
10
11 int a = 265 * k;
12
13 int b = 191 * k;
14
15 int c = 148 * k;
16
17 int d = 129 * k;
18
19 int e = 76 * k;
20
21 Console.WriteLine("a={0},b={1},c={2},d={3},e={4} ------h={5}\n", a, b, c, d, e, h);
22 }
23
24 Console.Read();
25 }
26 }
27 }
相信大家以后遇到类似的问题,应该会胸有成竹了。