乐山师范学院ACM集训队专题一枚举

别等时光非礼了梦想. 提交于 2021-01-21 12:37:08

专题一:枚举

枚举的优化:

  • 根据题意,缩短枚举层数,或者缩短每层枚举范围。
  • 如果有可以通过少量的局部枚举从而确定整个全部的情况,则就可以优化枚举。

例题一.

Perfect Cubes
题目大意:找满足此等式的所有情况 a ∗ a ∗ a = b ∗ b ∗ b + c ∗ c ∗ c + d ∗ d ∗ d , ( n ≥ a > d ≥ c ≥ b ≥ 1 ) , ( a , b , c , d ∈ Z ) a*a*a=b*b*b+c*c*c+d*d*d,(n\ge a>d\ge c\ge b\ge1),(a,b,c,d\in Z) aaa=bbb+ccc+ddd,(na>dcb1),(a,b,c,dZ)
并以非降序的a,b,c,d来输出。
输出格式:


Cube = a, Triple = (b,c,d)

代码:

#include<iostream>
#include<cstdio>

using namespace std;

int main(){
	int n;
	cin>>n;
	for(int a=2;a<=n;++a){       .//缩短了每层枚举的范围,从而优化了枚举算法
		for(int b=2;b<a;++b)
			for(int c=b;c<a;++c)
				for(int d=c;d<a;++d)
					if(a*a*a==b*b*b+c*c*c+d*d*d)
						printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
	}
	return 0;
}

例题二

Biorhythms
题目大意:人都有体力、情商、智商高峰期,每个周期分别为23、28、33天。现在你已知体力、情商、智商的开始日期,请你写出程序解决下一个体力、情商、智商三大高峰期位于同一天的日子距离d天过了多少天?
设p、e、i分别代表体力、情商、智商开始的日期,k代表下一个三大高峰期同期的日期。
这道题可以用(k-p)%23==0&&(k-e)%28==0&&(k-i)%33==0来检测k天时是否为三大高峰期的日期。但是如果这样每个每个试的话,枚举次数会很多,所以可能超时。
所以可以通过



  1. 先找到下一个体力高峰期
  2. 然后以每个体力高峰期枚举找到下一个情商高峰期
  3. 最后以每个体力、情商同高峰期(枚举体力周期和情商周期的最大公倍数)枚举找到下一个之所高峰期。
    这样就可以更快的找到三大高峰期的日子。

代码如下:

#include<iostream>
using namespace std;
int main(){
int p,e,i,d,caseNo=0;
	while(cin>>p>>e>>i>>d&&p!=-1){
		int k=d+1;
		caseNo++;
		for(;(k-p)%23!=0;k++);
		for(;(k-e)%28!=0;k+=23);
		for(;(k-i)%33!=0;k+=23*28);
		cout<<"Case "<<caseNo<<": the next triple peak occurs in "<<k-d<<" days.\n"
	}
}

例题三

Counterfeit Dollar
题目大意:有十二枚硬币,其中有一个假币,不知道是哪一枚,也不知道是重是轻。但是有三次称量,已知每次称量两边是那些硬币和结果。数据确保一定能找到结果,请你写出程序,找出是哪枚硬币并且它是重是轻?
此题也是枚举,枚举枚硬币,并且枚举它是重是轻,如果三次称量都满足它是假币,则输出答案。

代码如下:

#include<iostream>
#include<cstring>
using namespace std;
char L[3][7];
char R[3][7];
char res[3][7];
bool isFake(char c,bool light){
	for(int i=0;i<3;i++){
		char *pl,*pr;
		if(light){
			pl=L[i];
			pr=R[i];
		}else {
			pl=R[i];
			pr=L[i];
		}
		switch(res[i][0]){
			case 'u':if(strchr(pr,c)==NULL) return false;
				break;
			case 'd':if(strchr(pl,c)==NULL) return false; 
				break;
			case 'e':if(strchr(pl,c)==NULL||strchr(pr,c)==NULL) return false;
				break;
		}
	}
	return true;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		for(int i=0;i<3;i++) cin>>L[i]>>R[i]>>res[i];
		for(char ch='A';ch<='L';++ch){
			if(isFake(ch,true)){
				cout<<ch<<" is the counterfeit coin and it is light.\n";
			}else if(isFake(ch,false)){
				cout<<ch<<" is the counterfeit coin and it is heavy.\n";
			}
		}
	}
	return 0;
}

例题四

EXTENDED LIGHTS OUT
题目大意:已知一个5 X 6的电灯泡排列成一个矩阵,知道了每个电灯泡的初始状态。现在可以通过操作电灯泡改变电灯泡的状态(由关到开或在由开到关),而且每次操作可以改变不止一个电灯泡的状态。每次操作会改变电灯泡位置的上下左右四个电灯泡及该位置上的电灯泡的状态。而如果电灯泡在边角则不会改变越界的位置。那么在已知初始状态后,操作那些灯泡可以使所有灯泡全熄灭?

此题因为有30个灯泡,如果枚举所有操作的可能要枚举 2 30 2^{30} 230种,所以肯定会超时的。那么有没有什么局部的枚举就可以决定后面的情况呢?答案是有的。如果枚举第一行的操作,那么第一行在熄灭后剩余没有熄灭的灯就只能由第二行的灯泡操作来熄灭,而第二行的灯操作后剩余的灯也就只能被下一行操作熄灭,如果最后一行都是状态关,则就是要的操作,并输出结果。而这仅仅只需要 2 5 2^5 25=32次枚举。

代码如下:

#include<iostream>
#include<memory>
#include<cstring>
#include<string>
using namespace std;
int GetBit(char c,int i){
	return (c>>i)&1;
}
void SetBit(char &c,int i,int v){
	if(v) c |= (1<<i);
	else c &= ~(1<<i);
}
void Flip(char & c,int i){
	c ^= ( 1<<i );
}
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(){
	char oriLights[5];
	char lights[5];
	char result[5];
	char switchs;
	int t;
	cin>>t;
	for(int i=1;i<=t;i++){
		memset(oriLights,0,sizeof(oriLights));
		for(int i=0;i<5;i++){
			for(int j=0;j<6;j++){
				int s;
				cin>>s;
				SetBit(oriLights[i],j,s);
			}
		}
		for(int n=0;n<64;n++){
			memcpy(Lights,oriLights,sizeof(oriLights));
			switchs=n;
			for(int i=0;i<5;i++){
				result[i]=switchs;
				for(int j=0;j<6;j++){
					if(GetBit(switchs,j)){
						if(j>0) Flip(lights[i],j-1);
						Flip(lights[i],j);
						if(j<5) Flip(lights[i],j+1);
					}
				}
				if(i<4) lights[i+1] ^= switchs;
				switchs=lights[i];
			}
			if(lights[4]=0){	
					OutputResult(t,result);
					break;
			}
		}
	}
	return 0;
}

例题五

Preparing Olympiad
题目大意:现在有n个数( 1 ≤ n ≤ 15 1\le n \le 15 1n15),给你一个范围[l,r]和一个整数x。你从n个数中选任意数字组合,如果这个数组总和在[l,r]中并且满足最大值减去最小值不小于x,则统计这种数字组合有多少种?

其实就是枚举所有数字组合,然后计数就行,但是难在如何枚举?因为所有数字在数字组合中都只有两种状态(取与不取),所以可以用一个n位的二进制数来描述数字组合。

代码如下:

#include<iostream>
using namespace std;
int a[20];
int getbit (int num,int i){
	return (num>>i)&1;
}
int main(){
	int n,lim,cnt=0,r,l,x;
	cin>>n>>l>>r>>x;
	lim=1<<n;
	for(int i=0;i<n;i++) cin>>a[i];
	for(int i=0;i<=lim;i++){
		int sum=0,maxa=-1,mina=1e9;
		for(int j=0;j<n;j++){
			if(getbit(i,j)){
				sum+=a[j];
				maxa=max(maxa,a[j]);
				mina=min(mina,a[j]);
			}
		}
		if(sum>=l&&sum<=r&&maxa-minn>=x) cnt++;
	}
	cout<<cnt;
	return 0;
}


总结一下,只要时间复杂度够,就枚举吧!简单粗暴。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!