穷举(四):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”的四种源程序的评判情况
上一篇: JavaScript时间与时间戳的转换操作实例分析
下一篇: 差点被领导发现早退