穷举(四):POJ上的两道穷举例题POJ 1411和POJ 1753

下面给出两道POJ上的问题,看如何用穷举法解决。

【例9】Calling Extraterrestrial Intelligence Again(POJ 1411)

Description

A message from humans to extraterrestrial intelligence was sent through the Arecibo radio telescope in Puerto Rico on the afternoon of Saturday November 16, 1974. The message consisted of 1679 bits and was meant to be translated to a rectangular picture with 23 x 73 pixels. Since both 23 and 73 are prime numbers, 23 x 73 is the unique possible size of the translated rectangular picture each edge of which is longer than 1 pixel. Of course, there was no guarantee that the receivers would try to translate the message to a rectangular picture. Even if they would, they might put the pixels into the rectangle incorrectly. The senders of the Arecibo message were optimistic.

We are planning a similar project. Your task in the project is to find the most suitable width and height of the translated rectangular picture. The term "most suitable" is defined as follows. An integer m greater than 4 is given. A positive fraction a/b less than or equal to 1 is also given. The area of the picture should not be greater than m. Both of the width and the height of the translated picture should be prime numbers. The ratio of the width to the height should not be less than a/b nor greater than 1. You should maximize the area of the picture under these constraints.

In other words, you will receive an integer m and a fraction a/b. It holds that m > 4 and 0 < a/b <= 1. You should find the pair of prime numbers p, q such that pq <= m and a/b <= p/q <= 1, and furthermore, the product pq takes the maximum value among such pairs of two prime numbers. You should report p and q as the "most suitable" width and height of the translated picture.

Input

The input is a sequence of at most 2000 triplets of positive integers, delimited by a space character in between. Each line contains a single triplet. The sequence is followed by a triplet of zeros, 0 0 0, which indicates the end of the input and should not be treated as data to be processed.

The integers of each input triplet are the integer m, the numerator a, and the denominator b described above, in this order. You may assume 4 < m <= 100000 and 1 <= a <= b <= 1000.

Output

The output is a sequence of pairs of positive integers. The i-th output pair corresponds to the i-th input triplet. The integers of each output pair are the width p and the height q described above, in this order.

Each output line contains a single pair. A space character is put between the integers as a delimiter. No other characters should appear in the output.

Sample Input

5 1 2

99999 999 999

1680 5 16

1970 1 1

2002 4 11

0 0 0

Sample Output

2 2

313 313

23 73

43 43

37 53

(1)编程思路。

对于输入的m,通过穷举找到一组质数对(p,q),满足条件:1)p、q均为质数,且p<=q<=m;2)p*q<=m;3)a/b <= p/q;4)p*q取得最大值。

首先确定p和q的穷举范围,由于m不超过100000,因此p和q不会超过50000(因为最小的质数为2,2*50000=100000)。进一步考虑,由于1<=a<=b<=1000,所以a/b的最小值为1/1000=0.001,因此,p/q>=0.001。而当m达到最大100000时,100000/11=9090.9,质数2、3、5、7除以9091均小于0.001,因此,可将p、q的范围进一步缩小到取2~9091之间的质数。

先将小于9092的所有质数求出来,并存放在数组prime中,记下质数的个数num。

用二重循环穷举所有的p(prime[0]~prime[num-1])、q(p~prime[num-1])组合,对每种组合去判断是否符合条件,如果满足,将p*q与当前最大值max比较,保存最大的p*q。

(2)源程序。

#include <iostream>

using namespace std;

int main()

{

int flag[9092]={0},prime[5000]={0};

int num=0,m,a,b,p,q,max,i,j;

double s;

for (i=2;i<9092;i++)

{

if (flag[i]==0)

{

prime[num++]=i;

for (j=2*i;j<9092;j+=i)

flag[j]=1;

}

}

cin>>m>>a>>b;

while(m>0)

{

max=0;

s=1.0*a/b;

for(i=0; i<=num-1; i++)

{

if(prime[i]>m) break;

for(j=i;j<=num-1;j++)

{

if(prime[j]>m||prime[j]*prime[i]>m||1.0*prime[i]/prime[j]<s)

break;

if(prime[j]*prime[i]>max)

{

max=prime[i]*prime[j];

p=prime[i];

q=prime[j];

}

}

}

cout<<p<<" "<<q<<endl;

cin>>m>>a>>b;

}

return 0;

}

【例10】Flip Game(POJ 1753)

Description

Flip game is played on a rectangular 4x4 field with two-sided pieces placed on each of its 16 squares. One side of each piece is white and the other one is black and each piece is lying either it‘s black or white side up. Each round you flip 3 to 5 pieces, thus changing the color of their upper side from black to white and vice versa. The pieces to be flipped are chosen every round according to the following rules:

1. Choose any one of the 16 pieces.

2. Flip the chosen piece and also all adjacent pieces to the left, to the right, to the top, and to the bottom of the chosen piece (if there are any).

Consider the following position as an example:

bwbw

wwww

bbwb

bwwb

Here "b" denotes pieces lying their black side up and "w" denotes pieces lying their white side up. If we choose to flip the 1st piece from the 3rd row (this choice is shown at the picture), then the field will become:

bwbw

bwww

wwwb

wwwb

The goal of the game is to flip either all pieces white side up or all pieces black side up. You are to write a program that will search for the minimum number of rounds needed to achieve this goal.

Input

The input consists of 4 lines with 4 characters "w" or "b" each that denote game field position.

Output

Write to the output file a single integer number - the minimum number of rounds needed to achieve the goal of the game from the given position. If the goal is initially achieved, then write 0. If it‘s impossible to achieve the goal, then write the word "Impossible" (without quotes).

Sample Input

bwwb

bbwb

bwwb

bwww

Sample Output

4

(1)编程思路1。

因为任何一个格子翻偶数次和翻0次的效果是一样的,翻奇数次和翻1次的效果也是一样的。因此,一个格子要么翻1次,要么不翻。另外翻格子的顺序对最终的结果是没有影响的,也就是说先翻一号格子再翻二号格子和先翻二号格子再翻一号格子的效果是一样。

这样,可以对十六个格子中的每个格子翻或不翻两种情况进行穷举,总的穷举次数为216=65536次。

用一个一维数组int bits[16]来保存16个格子的初始状态,黑色的格子置为1,白色的置为0。则题目中图示的状态可描述为bits[16]={ 1,0,1,0,0,0,0,0,1,1,0,1,1,0,0,1}。

题目图示翻转格子bits[8],需进行操作

bits[8]=!bits[8]=0,

bits[4]=!bits[4]=1,     (bits[i - 4] = !(bits[i - 4]);)

bits[9]=!bits[9]=0,     (bits[i + 1] = !(bits[i + 1]);)

bits[12]=!bits[12]=0,   (bits[i + 4] = !(bits[i + 4]);)

由于格子8处于最左边,它的左边没有格子,因此bits[i - 1] = !(bits[i - 1])不能操作。

这样,翻转格子8后,状态变为{ 1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,1}。

用变量minCnt记录所需的最小的翻格子个数,初始值设为17,因为最多翻16个格子。

用循环 for(i = 0; i<65536; i++)对16个格子的翻或不翻的组合情况进行穷举。对每种情况i,判断i对应的二进制数中的第j位(最低位设为0位,则0<=j<=15)是否为1,如果为1,则翻转格子j。一种组合情况翻转完成后,判断当前状态是否为纯色,如果是,记录所翻转格子数(实际上是整数i对应二进制数中1的个数)的最小值。

(2)源程序1。

#include <iostream>

using namespace std;

// 判断当前格局是否为纯色

int isSolidColor(int bits[], int len)

{

int i = 0;

for (i = 0; i < len - 1; i++)

if (bits[i] != bits[i + 1])

return 0;

return 1;

}

// 改变第i个格子及其周围格子的颜色(0<=i<=15)

void changeColor(int bits[], int i)

{

bits[i] = !(bits[i]);

int x = i/4;

int y = i%4;

if (y < 3)

bits[i + 1] = !(bits[i + 1]);

if (y > 0)

bits[i - 1] = !(bits[i - 1]);

if (x > 0)

bits[i - 4] = !(bits[i - 4]);

if (x < 3)

bits[i + 4] = !(bits[i + 4]);

}

int main()

{

char c;

int bits[16],new_bits[16];

int cnt,minCnt = 17;

int i,j,k;

for(i = 0; i<16; i++)           // 将输入的格局保存到数组bits中

{

cin >> c;

if(c == ‘b‘)

bits[i] = 1;

else

bits[i] = 0;

}

for(i = 0; i<65536; i++)      // 对65536种组合情况进行穷举

{

for (j = 0; j < 16; j++)    // 为保证初始格局不被修改,缓存到new_bits

new_bits[j] = bits[j];

cnt = 0;

k=1;

for (j = 0;j < 16;j++)     // 对当前组合情况的16个二进制位进行穷举

{

if (i & k)          // 当前j位为1,则需翻转格子j,并计数

{

cnt++;

changeColor(new_bits, j);

}

k=k<<1;

}

if (isSolidColor(new_bits, 16))   // 判断是否达到纯色

if (cnt < minCnt)  minCnt = cnt;

}

if (minCnt==17) cout << "Impossible" << endl;

else  cout << minCnt << endl;

return 0;

}

(3)编程思路2。

在上面的思路1中,用数组bits[16]来保存一种格局。实际上,可以用一个0~65535之间的整数value来保存一种格局。该整数value对应的16个二进制数位中为1的位j代表格子j是黑色。由于一个16位的二进制数从低位到高位的权值依次为1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,因此,将一种格局映射为value值可用一个简单循环来实现:

char c;

for(i = 0; i<16; i++)

{

cin >> c;

if(c == ‘b‘)

value += bitVal[i];

}

其中,int bitVal[16] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};

例如,题目图示所给出的格局映射为的value值为39685,对应二进制数为1001101100000101。即39685=32768+4096+2048+512+256+4+1。

翻转一个格子是将它及其周围的格子翻转,可以看成对应二进制位变反。将一个整数的某个二进制位变反可以通过对该位用1进行异或运算实现。因此,对于当前的格局value,若要翻转某个格子i(0<=i<=15),只需将value异或一个特定的整数c[i]即可。

例如,按题目的格局value(值为39685),要翻转第8个格子,由于涉及到4个格子的变反,异或的11二进制数值应为1001100010000,及0x1310。

39685^(0x1310)的值34837,对应二进制数为1000100000010101,转换为格局为

bwbw

bwww

wwwb

wwwb

用一个数组cTable[16]来保存翻转每个格子应异或的特定整数,该数组初始化为:

int cTable[16] = {0x13,0x27,0x4E,0x8C,0x131,0x272,0x4E4,0x8C8,0x1310,0x2320,

0x4E40,0x8C80,0x3100,0x7200,0xE400,0xC800};

同样用循环for(i = 0; i<65536; i++)对16个格子的翻或不翻的组合情况进行穷举。对于穷举的每组情况,记录每一次使得value == 0||value == 65535的翻格子数,保存翻格子数目最小的数值。

(4)源程序2。

#include <iostream>

using namespace std;

int main()

{

int cTable[16] = {0x13,0x27,0x4E,0x8C,0x131,0x272,0x4E4,0x8C8,0x1310,0x2320,

0x4E40,0x8C80,0x3100,0x7200,0xE400,0xC800};

int bitVal[16] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};

int i,j,value = 0, chgVal;

int cnt,minCnt = 17;

char c;

for(i = 0; i<16; i++)

{

cin >> c;

if(c == ‘b‘)

value += bitVal[i];

}

for(i = 0; i<65536; i++)

{

cnt = 0;

chgVal = value;

for (j = 0;j < 16;j++)

if (i & bitVal[j])

{

cnt++;

chgVal ^= cTable[j];

}

if(chgVal==0 || chgVal==65535)

if (cnt < minCnt)  minCnt = cnt;

}

if (minCnt==17) cout << "Impossible" << endl;

else  cout << minCnt << endl;

return 0;

}

(5)换一种思路穷举。

前面的两种思路是按0~65535的顺序穷举,这样的顺序所选取的格子数目是不连续的。而题目要得到的是所需翻转的最少的格子数。因此,穷举的顺序最好从格子的数目这一角度出发。即当0个格子被翻转时,看对应的格局是否为纯色;否则选择1个格子进行翻转(有16种选择),看翻转后的格局是否为纯色;否则再选择2个格子进行翻转(有120种选择),看翻转后的格局是否为纯色;……;最后选择16个格子进行翻转。在这个过程中,只要有纯色的格局出现,就结束穷举过程,输出相应的被选择的格子个数。如果16个格子都被翻转了,还是没变成纯色,则输出“Impossible”。

因此,这种思路的关键问题是怎样得到从16个格子中选取n个格子的所有组合。

(6)组合情况的递归实现。

设将从1~len共len个数中选取count个不同的数,并存放于数组take中的操作抽象为函数void combineTest(int take[], int len, int count,const),则这个操作过程可以采用递归实现:

在count~len中选择一个数i(count<=i<=len)赋给take[count-1];(之所以选择的最小数为count,是因为要选出count个数,选了一个大数i的话,得留比i小的count-1个数)

再从1~i共i个数中选取count-1个不同的数,即递归调用combineTest(take, i - 1, count – 1);

递归结束条件为count==1。

编写的递归函数如下:

void combineTest(int take[], int len, int count,const int NUM)

{

int i,j;

for (i = len; i >= count; i--)

{

take[count - 1] = i;

if (count >1)

combineTest(take, i - 1, count - 1, NUM);

else

{

for (j = NUM - 1; j >=0; j--)

{

cout<<take[j]<<"  ";

}

cout<<endl;

}

}

}

编写如下的程序段来测试递归函数combineTest。

int n,m;

cin>>n>>m;

int *take = new int [m];

combineTest(take,n, m, m);

delete  [] take;

运行这段程序,输入5和3,可得到如下所示的结果。

5

3

5  4  3

5  4  2

5  4  1

5  3  2

5  3  1

5  2  1

4  3  2

4  3  1

4  2  1

3  2  1

Press any key to continue

(7)按新思路改写思路1的源程序3。

#include <iostream>

using namespace std;

// 判断当前格局是否为纯色

int isSolidColor(int bits[], int len)

{

int i = 0;

for (i = 0; i < len - 1; i++)

if (bits[i] != bits[i + 1])

return 0;

return 1;

}

// 改变第i个格子及其周围格子的颜色(0<=i<=15)

void changeColor(int bits[], int i)

{

bits[i] = !(bits[i]);

int x = i/4;

int y = i%4;

if (y < 3)

bits[i + 1] = !(bits[i + 1]);

if (y > 0)

bits[i - 1] = !(bits[i - 1]);

if (x > 0)

bits[i - 4] = !(bits[i - 4]);

if (x < 3)

bits[i + 4] = !(bits[i + 4]);

}

void combine(int take[], int len, int count, const int NUM, int &cnt, int bits[])

{

int i;

for (i = len; i >= count; i--)

{

if (cnt!=0)  break;

take[count - 1] = i - 1;

if (count > 1)

combine(take, i-1, count-1, NUM, cnt, bits);

else

{

int j = 0;

int new_bits[16];

for (j = 0; j < 16; j++)

new_bits[j] = bits[j];

for (j = NUM - 1; j >=0; j--)

{

changeColor(new_bits, take[j]);

}

if (isSolidColor(new_bits, 16))

cnt = NUM;

}

}

}

int main()

{

char c;

int bits[16];

int i=0;

for(i = 0; i<16; i++)

{

cin >> c;

if(c == ‘b‘)

bits[i] = 1;

else

bits[i] = 0;

}

if (isSolidColor(bits, 16))

cout<<0<<endl;

else

{

int j;

for (j = 1; j <= 16; j++)

{

int *take = new int [j];

int cnt = 0;

combine(take,16, j, j, cnt, bits);

if (cnt==j)

{

cout<<cnt<<endl;

delete  [] take;

break;

}

delete  [] take;

}

if (j == 17)

cout<<"Impossible"<<endl;

}

return 0;

}

(8)按新思路改写思路2的源程序4。

#include <iostream>

using namespace std;

int cTable[16] = {0x13,0x27,0x4E,0x8C,0x131,0x272,0x4E4,0x8C8,0x1310,0x2320,

0x4E40,0x8C80,0x3100,0x7200,0xE400,0xC800};

int bitVal[16] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};

void combine(int take[], int len, int count, const int NUM, int &cnt, int value)

{

int i,j,chgVal;

for (i = len; i >= count; i--)

{

if (cnt!=0)  break;

take[count - 1] = i - 1;

if (count > 1)

combine(take, i-1, count-1, NUM, cnt, value);

else

{

chgVal = value;

for (j = NUM - 1; j >=0; j--)

chgVal ^= cTable[take[j]];

if(chgVal==0 || chgVal==65535)

cnt = NUM;

}

}

}

int main()

{

int i,j,cnt,value = 0;

char c;

for(i = 0; i<16; i++)

{

cin >> c;

if(c == ‘b‘)

value += bitVal[i];

}

if(value==0 || value==65535)

cout<<0<<endl;

else

{

for (j = 1; j <= 16; j++)

{

cnt = 0;

int *take = new int [j];

combine(take,16, j, j, cnt, value);

if (cnt==j)

{

cout<<cnt<<endl;

delete  [] take;

break;

}

delete  [] take;

}

if (j == 17)

cout<<"Impossible"<<endl;

}

return 0;

}

将上述四个源程序依次提交到POJ,可得到如图1所示的结果。由图可以看出,源程序4的执行效率最好。

图1  POJ显示的解决“问题1753”的四种源程序的评判情况

原文地址:https://www.cnblogs.com/cs-whut/p/11015718.html

时间: 2024-08-09 18:52:42

穷举(四):POJ上的两道穷举例题POJ 1411和POJ 1753的相关文章

POJ 1573 &amp; POJ 2632(两道有趣的Robot)

题目链接:POJ 1573 Robot Motion & POJ 2632 Crashing Robots [题意]题意就不说了,有兴趣从链接点进去看吧,就是机器人各种打扫房间,行驶指令. [思路]2632是一道纯模拟题,只要把题意读懂,就可以用代码模拟过程,只是写起来有点蛋疼,代码力还是欠缺啊.而1573感觉挺新奇的,我用的DFS来模拟,好久没有写DFS,一开始又把dir数组写错了,结果总是得出0. 下面贴代码,先是2632的AC代码: 1 /* 2 ** POJ 2632 Crashing

两道二分coming~

第一道:poj 1905Expanding Rods 题意:两道墙(距离L)之间架一根棒子,棒子受热会变长,弯曲,长度变化满足公式( s=(1+n*C)*L),求的是弯曲的高度h. 首先来看这个图: 如图,蓝色为杆弯曲前,长度为L 红色为杆弯曲后,长度为s h是所求. 又从图中得到三条关系式; (1)       角度→弧度公式  θr = 1/2*s (2)       三角函数公式  sinθ= 1/2*L/r (3)       勾股定理  r^2 – ( r – h)^2 = (1/2*

POJ-1088滑雪,典型的动态规划题,与NYOJ-10skiing一样,但NYOJ上时限是3s,用搜索可以过,但在POJ上就超时了~~

滑雪 Time Limit: 1000MS                    Memory Limit: 65536k                                               http://poj.org/problem?id=1088            Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Michael想知道

告诉我图样图森破的两道简单C++笔试题

今晚刷了一大堆的笔试题,中规中矩,但是有两道做得很快但是都错了的题目,印象深刻. (要找工作的大四渣有没有共鸣,在学校明明很努力,但是总是跟不上时代,没有厉害的项目,也没有过人的竞赛成绩,内推屡屡失败,前天阿里巴巴在线笔试也被虐死,真心迷惘,唯独刷题搞笔试了.) 第一道题是关于宏定义的. #include<iostream> using namespace std; #define fun(n) (n-1)*n int main() { int x=3; cout<<fun(x+3

在ubuntu16.04上安装有道词典

参考来源:http://www.cnblogs.com/scplee/archive/2016/05/13/5489024.html 在ubuntu16.04上安装有道词典主要是解决依赖问题,我们在配置文件中把依赖文件删除,就行了 第一步:下载有道词典的.deb包 第二步:创建youdao文件,然后把有道的源文件解压到这个包里 sudo dpkg -X sudo dpkg -X ./youdao-dict_1.1.0-0-ubuntu_amd64.deb youdao 第二步:解压deb包中的c

水了两道括号匹配

POJ 1141 给一段括号序列,要求增加最少的括号,使之合法,输出序列. dp[i][j]表示使给定序列的i到j成为合法序列所需添加的最少括号数,dp[0][length-1]即是答案,转移的话,如果s[i]和s[j]可以匹配那么dp[i][j] = dp[i+1][j-1],否则就考虑在中间选择一个位置m,使分割成的两个序列各自成为合法序列.方案的话就是多开一个数组记录然后递归输出.状态是从长度小的序列转移到长度长的序列,所以两层循环,外层枚举长度,内层枚举头位置即可.写成记忆化搜索简单一点

Linkit 7688 DUO(四): 接上各种Arduino传感器和模块——基础篇

前一篇讲了 Linkit 7688DUO操作Arduino的原理和基本方法.现在,我们要为开发板接上各类Arduino的传感器和模块了,这些模块提供了各类输入输出. 一.首先要充分了解 Linkit 7688 DUO开发板的引出管脚 Linkit 7688 DUO开发板上有两个处理器芯片. 一片是  Linkit 7688, 主处理器 一片是  ATmega32U4,  这是Arduino的处理芯片,提供Arduino编程接口,用于控制传感器外设等 两个处理器通过内部串口相连. 在开发中, 要写

算法--两道百度笔试题

算法--两道百度笔试题 今天看到一位园友写了一篇关于百度的面试题的博客,成了评论头条,再下看了一下,非常感兴趣,那位博主的算法能力跟我一样需要提高,估计他的功力还在我之下,所以再下不才,在这里把自己的源码贴出来. 百度面试题(一):假设一整型数组存在若干正数和负数,现在通过某种算法使得该数组的所有负数在正数的左边,且保证负数和正数间元素相对位置不变.时空复杂度要求分别为:o(n)和o(1).          其实开始的时候我也是一头雾水,在纸上画画之后发现,其实就是一道变形的插入排序.幸运的是

java微信公众平台开发四(上传素材)

最近公司要做微信方面的开发,今天说下,如何使用微信的素材管理的接口,这里主要讲下素材的上传接口,下载之类的比较简单(就是解析json而已),今天会把所有的素材上传写道一个方法里供大家参考,关于上传的接口文档我就不粘贴了,直接上代码! /** * 这里说下,在上传视频素材的时候,微信说不超过20M,我试了下,超过10M调通的可能性都比较小,建议大家上传视频素材的大小小于10M比交好 * @param accessToken * @param file 上传的文件 * @param title 上传