简介
- 基于逐个尝试答案的一种问题求解策略
例题——完美立方
给一个n,找到n以内的所有a3=b3+c3+d3
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
ll V3(int n){
return n*n*n;
}
int main(){
int n;
cin>>n;
ll num[n+1];
for(int i=1;i<=n;i++){
num[i]=V3(i);//比较下层反复计算,消耗一些空间换时间
}
int a,b,c,d;
for(a=3;a<=n;a++){
for(b=2;b<=a-1;b++){
for(c=b;c<=a-1;c++){
for(d=c;d<=a-1;d++){
if(num[a]==num[b]+num[c]+num[d]){
cout<<"Cube = "<<a<<",Triple = ("<<b<<","<<c<<','<<d<<")";
cout<<endl;
}
//注意这里四个数的取值范围,某一些范围可人为控制认为不可能,进而为程序提高效率
}
}
}
}
}
例题——生理周期
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int p,e,i,d;
while(cin>>p>>e>>i>>d&&p!=-1){
for(int a=33+i;a<=21252;a+=33){
//用这个头可以实现跳着枚举这个日子,使效率更高
if((a-e)%28==0&&(a-p)%23==0&&a>d){
cout<<a-d<<endl;
}
}
}
}
/*
while (cin>>p>>e>>i>>d&&p!=-1)
{
int k;
for(k=d+1;(k-p)%23;k++);//直到k是23的倍数就停止,而且保证肯定大于d
for(;(k-e)%28;k+=23);//保证是23的倍数,跳着找,找28倍数那一天
for(;(k-i)%33;k+=23*28);//保证是23和28的倍数,跳着找,找三个数倍数那一天
}
*/
例题——称硬币
/*已知三次称量结果,找出那枚假硬币
在枚举这一块是,先假设假币是更重还是更轻,再假设每一块币是假的时候,
是否符合三次测量结果,如果不符合,则下一个;*/
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
char leftstr[3][7];
char rightstr[3][7];
char resultstr[3][7];
bool IsFake(char c,bool IsHeavy){
//IsHeavy为真时,是假设假币更重
char *pleft,*pright;
for(int i=0;i<3;i++){
if(IsHeavy){
pleft=leftstr[i];
pright=rightstr[i];
}else{
pleft=rightstr[i];
pright=leftstr[i];
}
switch(resultstr[i][0]){
case 'u'://即右边比较轻,右边是up,重时假币在左边
if(strchr(pleft,c)==NULL){
return false;//即不符合,不在左边,这个币是假币不成立
}
break;
case 'd':
if(strchr(pright,c)==NULL){
return false;
}
break;
case 'e':
if(strchr(pright,c)||strchr(pleft,c)){
return false;
}
break;
}
}
return true;
}
int main(){
int time;
cin>>time;
while(time--){
for(int i=0;i<3;i++){
cin>>leftstr[i]>>rightstr[i]>>resultstr[i];
}
for(char c='A';c<='A'+12;c++){
if(IsFake(c,true)){
cout<<c<<"is fake.and it's heavy!"<<endl;
}else if (IsFake(c,false)){
cout<<c<<"is fake.and it's light!"<<endl;
}
}
}
}
例题——熄灯问题
用二进制数进行枚举的巧妙
因为开关灯的状态都是用1,0表示,用二进制可以达到一个缩减空间的作用
用6个比特就能存一行的灯
用一个int数,就能把一行里的所有可能表示完;而不用使用6个循环,反复变换1,0状态
/*
基本思路:如果存在某个局部,一旦这个局部的状态被确定,那么剩余其他部分的状态只能是确定的一种,
或者是为数不多的n种,那么就只需枚举这个局部的状态即可。
异或是:相同为0,不同为1
*/
#include<iostream>
#include<cstring>
using namespace std;
char oriLights[5];//一个元素对应一行的比特情况
char lights[5];//变化中灯的矩阵
char result[5];
//在这种情况下,所有操作都是对应着一个字符的比特进行
int GetBit(char c,int i){
return (c>>i) & 1;
//c<<i能使第i位比特数,移动到第0位
}//能够取字符c的第i个比特
void SetBit(char &c,int i,int v){
if(v){
c |= (1<<i);
}else{
c &= ~(1<<i);
}
}//能够把字符c的第i位比特,更改为v
void FlipBit(char &c,int i){
c ^= (1<<i);
}//能够把字符c的第i位比特翻转,0->1;1->0
void OutPutResult(int t,char result[]){
cout<<"PUZZLE #"<<t<<endl;
for(int i=0;i<5;i++){
for(int j=0;j<6;j++){
cout<<GetBit(result[i],j);
if(j<5){
cout<<" ";
}
}
cout<<endl;
}
}//输出结果
int main(){
int time;
cin>>time;
for(int t=1;t<=time;t++){
memset(oriLights,0,sizeof(oriLights));
for(int i=0;i<5;i++){
for(int j=0;j<6;j++){
int s;
cin>>s;//s表示第i行第j列比特的状态0,1
SetBit(oriLights[i],j,s);
}
}//所有数据读入
//接下来枚举第一行的所有可能状态,用0~2^6-1的int值表示
for(int n=0;n<64;n++){//这个n使操作的可能排列,不是灯状态的排列
char switchs=n;//switchs就表示上一行的操作状态
memcpy(lights,oriLights,sizeof(oriLights));
for(int i=0;i<5;i++){
result[i]=switchs;//会自动将int转换成char再转存进去
for(int j=0;j<6;j++){
if(GetBit(switchs,j)){//如果在j比特的位置进行了操作,为1,则影响周围灯状态
if(j>0) FlipBit(lights[i],j-1);
FlipBit(lights[i],j);
if(j<5) FlipBit(lights[i],j+1);
}//对同一i行的灯进行了处理
}
if(i<4) lights[i+1] ^= switchs;
//这一步就能把i+1的情况更改完成
//根据上一行灯的情况,更改下一行操作的情况;
//下一行的操作switchs必须关掉上一行还开着的灯
switchs=lights[i];
//上一行灯是1的状态,则下一行操作也必须是1进行反转
}
//最终能不能成功,取决于最后一行能不能全灭
if(lights[4]==0){
OutPutResult(t,result);
break;
}
}
}
}