递归逻辑
n的规模由n-1规模 计算得到
计算法则:
1、第n个取或不取 ( 二进制位运算 | 数组存储 )
解释为从1~n 进行枚举
二进制位运算 | 数组存储,记录取或不取的状态
2 15 2^{15} 215 = 32768
216 = 65536
220 约等于106
263 约等于 108
2、以 n=1 为基本状态,解释为情况组合
#include
using namespace std;
int n;
void dfs(int u,int state){//2^15 = 32768 state不超过 32768
// state第i个二进制位代表 i取不取
if(u==n+1){
for(int i=1;i<=n;i++){
if((state >> (i-1)) & 1){
cout<<i<<" ";
}
}
cout<<endl;
return ;
}
dfs(u+1,state);//u这个数不取
dfs(u+1,state | (1<<(u-1)));//二进制数 从0开始
}
int main(){
cin>>n;
dfs(1,0);
return 0;
}
#include
using namespace std;
int n;
const int N=20;
bool st[N];
void dfs(int u){
if(u==n+1){
for(int i=1;i<=n;i++){
if(st[i])cout<<i<<" ";
}
cout<<endl;
return ;
}
st[u]=false;
dfs(u+1);
st[u]=true;
dfs(u+1);
st[u]=false;
}
int main(){
cin>>n;
dfs(1);
return 0;
}
#include
using namespace std;
int n;
const int N=20;
bool st[N];
int res[N];
void dfs(int u){
if(u==n+1){
for(int i=1;i<=n;i++){
cout<<res[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=n;i++){//第u个数为i
if(!st[i]){
res[u]=i;
st[i]=true;
dfs(u+1);
st[i]=false;
}
}
}
int main(){
cin>>n;
dfs(1);
return 0;
}
#include
using namespace std;
int n,m;
const int N=20;
bool st[N];
void dfs(int u,int s){//从u开始取 u~n中,已经取了s个
if(s+n-u+1<m)return;//继续走下去选不够
if(s==m+1){
for(int i=1;i<=n;i++){
if(st[i])cout<<i<<' ';
}
cout<<endl;
return;
}
else if(u>n)return;
// 要求由小到大输出,那么小的数要先选
st[u]=true;
dfs(u+1,s+1);
st[u]=false;
dfs(u+1,s);
}
int main(){
cin>>n>>m;
dfs(1,0);
return 0;
}
#include
using namespace std;
int n,m;
void dfs(int u,int sum,int state){//2^15 = 32768 state不超过 32768
// state第i个二进制位代表 i取不取
if(sum+n-u+1<m)return;
if(sum==m){
for(int i=1;i<=n;i++){
if((state >> (i-1)) & 1){
cout<<i<<" ";
}
}
cout<<endl;
return ;
}
else if(u==n+1)return ;
dfs(u+1,sum+1,state | (1<<(u-1)));//二进制数 从0开始
dfs(u+1,sum,state);//u这个数不取
}
int main(){
cin>>n>>m;
dfs(1,0,0);//从1开始 ,已经选了0个,0代表选择的状态
return 0;
}
状态为参数保存在栈节点中 可行√
#include
#include
using namespace std;
int n,m;
struct node{
int pos;//在递归中的位置
int u,sum,state;//在栈中的内容
};
stack<node> stk;
void dfs(int u,int sum,int state){//2^15 = 32768 state不超过 32768
// state第i个二进制位代表 i取不取
// 0:
if(sum+n-u+1<m)return;
if(sum==m){
for(int i=1;i<=n;i++){
if((state >> (i-1)) & 1){
cout<<i<<" ";
}
}
cout<<endl;
return ;
}
else if(u==n+1)return ;
// 1:
dfs(u+1,sum+1,state | (1<<(u-1)));//二进制数 从0开始
// 2:
dfs(u+1,sum,state);//u这个数不取
}
int main(){
cin>>n>>m;
// dfs(1,0,0);//从1开始 ,已经选了0个,0代表选择的状态
stk.push({0,1,0,0});
while(!stk.empty()){
node t=stk.top();stk.pop();
int u=t.u;
int sum=t.sum;
int state=t.state;
if(t.pos==0){
if(sum+n-u+1<m)continue;
if(sum==m){
for(int i=1;i<=n;i++){
if((state >> (i-1)) & 1){
cout<<i<<" ";
}
}
cout<<endl;
continue ;
}
else if(u==n+1)continue ;
t.pos=1;
stk.push(t);
stk.push({0,u+1,sum+1,state | (1<<(u-1))});
}
else if(t.pos==1){
t.pos=2;
stk.push(t);
stk.push({0,u+1,sum,state});
}
else continue;
}
return 0;
}
状态存放在数组中 不科学× (因为递归写法,按照当前状态执行完接下来的步骤,而非递归状态,每个节点所依仗的当前状态不同,后出栈的节点等到出栈时,当前状态早已被覆盖改变)
#include
#include
using namespace std;
int n,m;
const int N=20;
bool st[N];
struct node{
int pos;//在递归中的位置
int u, s;//在栈中的内容
};
stack<node> stk;
void dfs(int u,int s){//从u开始取 u~n中,已经取了s个
// 0:
if(s+n-u+1<m)return;//继续走下去选不够
if(s==m+1){
for(int i=1;i<=n;i++){
if(st[i])cout<<i<<' ';
}
cout<<endl;
return;
}
else if(u>n)return;
// 要求由小到大输出,那么小的数要先选
st[u]=true;
dfs(u+1,s+1);
// 1:
st[u]=false;
dfs(u+1,s);
// 2:
}
int main(){
cin>>n>>m;
// dfs(0,0);
stk.push({0,0,0});//第一个参数0 表示 从0位置进去
while(!stk.empty()){
node t=stk.top();stk.pop();
int u=t.u;
int s=t.s;
if(t.pos==0){
if(s+n-u+1<m)continue;//继续走下去选不够
if(s==m+1){
for(int i=1;i<=n;i++){
if(st[i])cout<<i<<' ';
}
cout<<endl;
continue;
}
else if(u>n)continue;
t.pos=1;
stk.push(t);//继续往下走
st[u]=true;
stk.push({0,u+1,s+1});//相当于dfs(u+1,s+1);
}
else if(t.pos==1){
t.pos=2;
stk.push(t);//继续往下走
st[u]=false;
stk.push({0,u+1,s});//相当于dfs(u+1,s);
}
else continue;
}
return 0;
}
每个递归对应一个栈
1、用一个整数表示整个棋盘的状态
2、操作集合,暴力枚举所有动作方案
3、操作一个开关,本需要异或该行该列所有的数,利用change数组记录九个数的异或值(因为异或具有结合律,state ^ ( a1 ^ a2 ^……a9)
#include
#include
using namespace std;
const int N=10;
int change[N][N];
int get(int x,int y){
return 4*x+y;
}
#define PII pair<int,int>
vector<PII> res;
int main(){
string line;
int state=0;
for(int i=0;i<4;i++){
cin>>line;
for(int j=0;j<4;j++){
if(line[j]=='+')state+=(1<<(get(i,j)));
}
}
//计算 (i,j)开关时 影响九个值的异或值
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
for(int k=0;k<4;k++){
change[i][j]+= (1<<get(i,k));
change[i][j]+= (1<<get(k,j));
}
change[i][j]-= (1<<get(i,j));
}
}
for(int k=0;k<(1<<16);k++){//暴力枚举所有动作方案
int now=state;
vector<PII> tmp;
for(int i=0;i<16;i++){
if((k>>i)&1){
int x=i/4;
int y=i%4;
tmp.push_back({x,y});
now^=change[x][y];
}
}
if(!now && (res.size()==0 ||res.size()>tmp.size() )){
res=tmp;
}
}
cout<<res.size()<<endl;
for(int i=0;i<res.size();i++){
cout<<res[i].first+1<<" "<<res[i].second+1<<endl;
}
return 0;
}
因为c++除法是不看小数点后面的数字的,不转为乘法会出现 n=c+b(n=c,b=0的情况,列子1=1+2/3456789,满足除法,不满足条件,所以除法的题一般都要转乘法。)
n=a+b/c,枚举a,b,c,通过该等式判断枚举的组合是否ok
除法转为乘法(带除法式子判断时可能会因为1/999=0因精度致错) nc=ac+b,
第一种枚举很巧妙,abc 充分但是不重复利用1~9 要枚举所有满足条件的abc组合
是在将1~9的所有全排列求出,对于每一种排列去枚举不同的abc分割方法(分割3段)
第二种则是两层搜索(为了减少搜索层数,只搜ac,b通过等式计算出)
在搜出a的一种情况时,在此时情况(已用1-9多少位,a的值)的基础上,去搜索c值
即在走到dfsa的搜索树 的 叶子节点 时,去到 dfsc搜索树的root结点
#include
#include
#include
using namespace std;
const int N=15;
bool vis[N];
int v[N];
int n;
int cnt;
int cal(int l,int r){
int value=0;
for(int i=l;i<=r;i++)
value=value*10+v[i];
return value;
}
void dfs(int u){
if(u>9){
for(int i=1;i<=7;i++){//【1,9】分成3段 1~i i+1~j j+1~9
for(int j=i+1;j<=8;j++){
int a=cal(1,i);
int b=cal(i+1,j);
int c=cal(j+1,9);//abc都不能为0,j最多只取到第8位,第9位留给c
if(n*c==a*c+b){
cnt++;
}
}
}
return ;
}
for(int i=1;i<=9;i++){
if(!vis[i]){
vis[i]=true;
v[u]=i;
dfs(u+1);
vis[i]=false;
}
}
}
int main(){
cin>>n;
//n=a+b/c,枚举a,b,c,通过该等式判断枚举的组合是否ok
//除法转为乘法(带除法式子判断时可能会因为1/999=0因精度致错)
//n*c=a*c+b,
dfs(1);
cout<<cnt;
return 0;
}
//同样的把戏
#include
#include
#include
using namespace std;
const int N=1500;
bool vis[N];
bool vis_tmp[N];
//int v[N];
int n;
int cnt;
bool check(int b){
memcpy(vis_tmp,vis,sizeof(vis));
while(b){
int t=b%10;
b/=10;
if(vis_tmp[t]||t==0)return false; //是否重复
vis_tmp[t]=true;
}
for(int i=1;i<=9;i++){
if(!vis_tmp[i])return false;//是否充分
}
return true;
}
void dfs_c(int u,int a,int c){
if(u>8)return ;
int b=n*c-a*c;
if(!a||!b||!c){
// return;
}
else{
if(check(b))cnt++;
// return;
}
//这里不要轻易return,除了9位数已经全部用完
//c的位数不确定,c要继续dfs出别的值
for(int i=1;i<=9;i++){
if(!vis[i]){
vis[i]=true;
dfs_c(u+1,a,c*10+i);
vis[i]=false;
}
}
}
void dfs_a(int u,int a){//对a的搜索,已用多少位,a的值
if(u>7)return ;//至少留两位分别给bc
if(a>=n)return;//n*c=a*c+b 不能搞出负数
if(a){
dfs_c(u,a,0);
// return;a要继续dfs出别的值
}
for(int i=1;i<=9;i++){
if(!vis[i]){
vis[i]=true;
dfs_a(u+1,a*10+i);
vis[i]=false;
}
}
}
int main(){
cin>>n;
//n=a+b/c,枚举a,b,c,通过该等式判断枚举的组合是否ok
//除法转为乘法(带除法式子判断时可能会因为1/999=0因精度致错)
//n*c=a*c+b,
dfs_a(0,0);
cout<<cnt;
return 0;
}
//同样的把戏
费解在于,
很容易想到要枚举每一行的开关操作,为了点燃所有的灯(即一行一行操作下来最后要保证所有的灯燃着),自然不能 身在 i i i 行而针对第 i i i 行的情况去操作第 i i i 行的开关,因为很显然会让上一行(第 i i i 行)大乱。
因此只能通过对第 i i i 行的操作需要针对的是第 i − 1 i-1 i−1 行灯,即 通过控制第 i i i 行去点燃第 i − 1 i-1 i−1 行的灯,
自然想到,只需从第2行开始,其实不然,如果直接从第二行开始操作,那么推着推着会发现,这是一种唯一的固定的操作,永远是受牵制地按开关,而回到递推的源头——第一行的灯状态,于是,在最起初,可以枚举第一行灯的状态,以此为初态去递推,方可比较得出最小操作次数
错错错!不要看这段
这一费解点在代码中的体现是
for (int op = 0; op < 32; op ++ ) {
for (int i = 0; i < 5; i ++ ){
……
}
}
通过二进制数枚举每一行的开关状态,第1行是为了打乱第一行的灯的状态,为后面的递推创造初始状态,其余行则是为了点燃上一行的所有灯
不是暴力枚举每一行操作
而是只,暴力枚举第一行操作,第一行和其余行是分开操作的,最后一行要额外check是否全部点燃
观察 熄灯问题这种诘屈聱牙的写法也能看出(下一行的switchs由上一行决定),只有第一行的操作是通过枚举op操作的,其余行是在第一行的基础上继续进行的
#include
#include
#include
using namespace std;
int n;
const int N=10;
const int inf=0x3f3f3f3f;
//string light[N];
//string str[N];
char light[N][N];
char str[N][N];
int res=inf;
int dx[5]={0,0,0,1,-1};
int dy[5]={0,1,-1,0,0};
void flip(int x,int y){
for(int i=0;i<5;i++){
int cx=dx[i]+x;
int cy=dy[i]+y;
str[cx][cy]^=1;
}
}
int main(){
cin>>n;
while(n--){
res=inf;//多组输入
for(int i=0;i<5;i++){
cin>>light[i];
}
//不是暴力枚举每一行操作
//暴力枚举第一行操作
for(int op=0;op<(1<<5);op++){
memcpy(str,light,sizeof(light));//头文件 string.h ,老写错成strcpy
int cnt=0;
//i=0 第1行
for(int j=0;j<5;j++){//第1行
if((op>>j)&1){
flip(0,j);
cnt++;
}
}
//i=1~4 剩余行
for(int i=1;i<5;i++){
for(int j=0;j<5;j++){
if(str[i-1][j]=='0'){//千万不能写成 !str[i-1][j] 啊
flip(i,j);
cnt++;
}
}
}
//check最后一行
bool bright=true;
for(int j=0;j<5;j++){
if(str[4][j]=='0'){
bright=false;
break;
}
}
if(bright){
res=min(res,cnt);
}
}
if(res>6)cout<<"-1";
else cout<<res;
if(n)cout<<endl;
}
return 0;
}