指数型/排列型/组合型
,根据不同题目情形灵活应用
用dfs递归暴搜,重点在于,想一种枚举顺序,使其不重不漏
地输出每一种组合
从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案
输入样例:3
输出样例:
3
2
2 3
1
1 3
1 2
1 2 3
思路:有n个空位,每一个位置1选/0不选,一串长度为n的0/1序列为一个方案,从第一个开始做决策,第一个选(填1),接下来第二个选(填1)…一直到第n个选(填1),得到了一种方案,回溯,第n个不选(填0),又得到一种方案;回溯到上一层,第n-1不选(填0),继续往下走,想象顺着一棵树,一条路走到黑,然后又回一下头改变一下选择,再继续走到黑。
怎么写代码?
树中的每个位置状态需要记录,因为到了最后的叶子节点需要从跟一直读下来,作为一个方案。用dfs(1),表示从第1个位置开始做决策。
#include
#include
#include
#include
using namespace std;
const int N = 16;
int n;
int st[N];//记录每个位置状态 0:未考虑 1:选他 2:不选他
int ways[1<<15][16],cnt;
void dfs(int u)
{
if( u > n ){
//选完了,输出
for(int i = 1; i <= n; i++ )
{
if(st[i]==1)
//ways[cnt][i]=i;//记录每个方案
printf("%d ",i);
}
//cnt++;
printf("\n");
return;
}
st[u] = 2;
dfs(u + 1);//第一个分支 不选
st[u] = 0;//恢复现场
st[u] = 1;
dfs(u + 1);//第二个分支 选
st[u] = 0;
}
int main(){
cin>>n;
dfs(1);//从第1位开始选择
return 0;
}
95.费解的开关
一下子不知道如何下手,就从迈出第一步开始,我不知道究竟怎么改变可以让灯从全亮变成,最直接的,先对第一行动手,枚举在第一行上所能做的所有操作,此时这个枚举可以用指数型枚举的搜索模式,因为方案中每一个位代表选/不选,在本题中就可以当做按开关/不按开关。注意,
枚举的是对第一行的操作
,根据枚举的结果要对第一行进行一顿操作。继续分析发现,要是按照行的顺序来,第一行操作完之后,第二行的操作目标就是让第一行灭的灯亮起来,故第二行操作是固定的,以此类推,到了n-1行,最后一行无法操作了,若是全亮则此次尝试成功,否则不成功。
代码实现思路
- 枚举第一行操作
- 根据第一行操作,操作完1~n-1行
- 判断最后一行是不是全亮
注:
- 枚举还可以用
二进制方式
,从0~2n-1每一个数对应一个n位二进制,可以代表一种方案- 根据枚举的操作,如何实现turn(x,y)——事先规定好方向
代码
#include
#include
#include
#include
using namespace std;
const int N=6;
char g[N][N],backup[N][N];
int dx[5]={
-1,0,1,0,0},dy[5]={
0,1,0,-1,0};
void turn(int x,int y)
{
for(int i=0; i<5; i++ )
{
int a=x+dx[i],b=y+dy[i];
if(a<0 || a>=5 || b<0 || b>=5 ) continue;//出界
g[a][b]^=1;//二进制异或1,0变1askii码,1变0
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
for(int i=0; i<5; i++ ) cin>>g[i];//读入棋盘
int res=10;
for(int op=0; op<32; op++ )//枚举第一行所有操作方案
{
memcpy(backup,g, sizeof g);//备份
int step=0;
//操作第一行
for(int i=0;i< 5; i++ )
{
if(op>>i&1)
{
step++;
turn(0,i);
}
}
//下一行操作受限于上一行
for(int i=0; i<4; i++ )
{
for(int j=0;j<5;j++)
if(g[i][j]=='0')//若上一行某一格为‘0’,则下一行对应格子必定要按下,让该格子变为‘1’
{
step++;
turn(i+1,j);//按下
}
}
bool dark=false;//依次做完前4行后,判断最后一行是否符合要求
for(int i=0; i<5;i++)
{
if(g[4][i]=='0'){
dark=true;
break;
}
}
if(!dark) res=min(res,step);
memcpy(g,backup,sizeof g);
}
if(res>6) res=-1;
cout<< res<<endl;
}
return 0;
}
116.飞行员兄弟
看上去与费解的开关类似,又不一样,因为类似的,先枚举第一行操作后,下一行的操作并不能确定,还有很多行都可以对第一行造成影响,由于矩阵小,直接二进制枚举矩阵每一个单元的操作
#include
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
const int N =5;
char g[N][N];
char backup[N][N];
typedef pair<int,int> PII;
void turn_one(int x,int y)
{
if(g[x][y]=='+') g[x][y]='-';
else g[x][y]='+';
}
void turn_all(int x,int y)
{
for(int i=0;i<=3; i++)
{
turn_one(x,i);
turn_one(i,y);
}
turn_one(x,y);//消除重复的
}
int get(int x,int y)//返回x,y坐标的标号,为了可以按照字典序排序
{
return x*4+y;
}
int main()
{
for(int i=0;i<4;i++) cin>>g[i];//读状态
vector<PII> res;
for(int op=0;op< 1<<16 ; op++ )//对16个位置的所有方案进行枚举
{
memcpy(backup,g,sizeof g);
vector<PII> temp;
//进行操作
for(int i=0;i<=3;i++)//依次根据op的方案,按照op每个位的指示进行操作
for(int j=0;j<=3; j++)
if(op>>get(i,j)&1)//get(i,j)移到某一位查看op指示的操作
{
temp.push_back({
i,j});//有操作即记录
turn_all(i,j);
}
//判断所有开关是否全开
bool unfinish=false;
for(int i=0;i<=3;i++)
for(int j=0;j<=3;j++)
if(g[i][j]=='+')
unfinish=true;
if(!unfinish)
{
if(res.empty()|| res.size()> temp.size() ) res=temp;
}
memcpy(g,backup,sizeof g);
}
cout<<res.size()<<endl;
for(auto op :res ) cout<<op.x+1<<' '<<op.y+1<<endl;
return 0;
}
把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序
输入样例:3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
思路:重点还是想一个枚举的顺序,怎么不重不漏的写出来
- 先想好n个位置,依次枚举这个位置上放的数,例如,第一个放1,往下走第二个可以放除了1以外的其他数,直到位置放完,回溯,想象一棵树
- 先想一个数,把他依次放到每一个位置上,例如,先选1,1放1号位置,往下走,2放第2个…回溯1放第2个位置的情况
- 到叶子节点才会有一个方案出来
注:排列型枚举,在树往下生长的时候,要注意所选数字不能被重复利用,故增加一个判重的数组,记录i在这条路上是否用过used[i],因为每次对stu[u],u位置放数的时候都是从头开始找的。
代码怎么写? 与指数型类似
#include
#include
#include
#include
using namespace std;
const int N =10;
int n;
int st[N];// 0:未放置数,1~n表示放了那个数
bool used[N];//true 表示该数用过,false表示没有用过
void dfs(int u){
//表示现在探索u的位置放的数
if(u>n){
for(int i=1; i<=n; i++ ) printf("%d ",st[i]);//打印方案
puts("");
return;
}
//依次枚举每个分支,即当前位置可以填哪些数
for(int i=1;i<=n;i++)
{
if(!used[i]){
st[u]=i;
used[i]=true;
dfs(u+1);
//恢复现场
st[u]=0;
used[i]=false;
}
}
}
int main()
{
scanf("%d", &n);
dfs(1);
return 0;
}
1209.带分数
- 暴力把1~ 9切成三段,带入验证,先枚举1~9的全排列,用到
排列型枚举
,再双重循环砍两刀,获得a,b,c三个数,然后验证- 把数组里的数字组合起来,例如a[0]=1,a[1]=2,a[3]=[3],想得到123的话,只需要一重循环,循环内不断*10+a[i]即可,如下:
int cut(int s,int e)
{
int res=0;
for(int i=s;i<=e;i++)
res=res*10+st[i];
return res;
}
暴力代码
#include
#include
#include
#include
using namespace std;
const int N=10;
int st[N];
bool used[N];
int n;
int cnt;
int cut(int s,int e)
{
int res=0;
for(int i=s;i<=e;i++)
res=res*10+st[i];
return res;
}
void dfs(int u)
{
if(u==N)
{
for(int i=1;i<=10; i++ )
for(int j=i+1; j<=10; j++ )
{
int a=cut(1,i);
int b=cut(i+1,j);
int c=cut(j+1,9);
if(c*n==c*a+b) cnt++;
}
}
for(int i=1;i<=9; i++ )
{
if(!used[i])
{
st[u]=i;
used[i]=true;
dfs(u+1);
st[u]=-1;
used[i]=false;
}
}
}
int main()
{
cin>>n;
dfs(1);
cout<<cnt<<endl;
}
法2:双层嵌套dfs()
#include
#include
#include
#include
using namespace std;
const int N=20;
int n;
int ans;
bool used[N],backup[N];
bool check(int a,int c)
{
int b=n*c-a*c;
//看b中是否有重复数字
if(!a || !b || !c ) return false;
//把数组复制到backup里面,重开一个判重数组
memcpy(backup,used,sizeof used);
//分离b的每一位
while(b)
{
int x= b % 10;//取个位
b=b/10;//个位删掉
if(!x || backup[x]) return false;
backup[x]=true;
}
for(int i=1;i<=9; i++)
if(!backup[i])
return false;
return true;
}
void dfs_c(int u,int a, int c)
{
if(u==n) return;
if(check(a,c)) ans++;
for(int i=1;i<=9;i++)
{
if(!used[i])
{
used[i]=true;
dfs_c(u+1,a,c*10+i);
used[i]=false;
}
}
}
void dfs_a(int u,int a)
{
if(a>=n) return;
if(a) dfs_c(u,a,0);//对于每一个枚举的a都枚举c,相当于在a 的递归搜索树的叶子节点再开辟一个搜索树
for(int i=1;i<=9;i++)
{
if(!used[i])
{
used[i]=true;
dfs_a(u+1,a*10+i);
used[i]=false;
}
}
}
int main()
{
cin>>n;
dfs_a(0,0);
cout<<ans<<endl;
return 0;
}
从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案,要求每一行升序。
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
思路:与排列型枚举一致,不过限制了位置m个,并且升序的话,需要保证下一次的选取>父亲,从父亲的下一个数开始选取,故参数传递上多了一个父亲的值,少了一个判重数组
代码?
#include
#include
#include
#include
#include
using namespace std;
const int N =25;
int n,m;
int st[N];
vector< vector<int> > ways;
void dfs(int u,int limit)
{
if(u+n - limit < m ) return;//剪枝
if(u>m)
{
vector<int>way;
for(int i=1; i<= m; i++ )
{
way.push_back(st[i]);
}
ways.push_back(way);
return;
}
for(int i=limit;i<=n;i++)
{
st[u]=i;
dfs(u+1,i+1);
st[u]=0;
}
}
int main(){
scanf("%d %d",&n,&m);
dfs(1,1);
for(int i=0; i<ways.size();i++)
{
for(int j=0;j<ways[i].size();j++)
{
printf("%d ",ways[i][j]);
}
puts("");
}
return 0;
}
来源于y总的AcWing
(超棒的!