理论:
根据已确定的状态推导其他的状态。
在以确定的状态的基础上递推,不用绕弯,按部就班地推演就行能得到答案。
所以主要的问题就是确定数据的某些性质以及划定初始状态。
某些性质比如说最常见的“不是黑就是白”,“不是正就是反”,“不是开着就是关着”等;
例题:费解的开关(二维递推)
你玩过“拉灯”游戏吗?
25 盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
下面这种状态
10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:
01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:
01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。
输入格式
第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。
以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。
每组数据描述了一个游戏的初始状态。
各组数据间用一个空行分隔。
输出格式
一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。
数据范围
0<n≤500
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
代码与详解:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510;
int g[6][6],bg[6][6];
int dx[5]={0,0,0,-1,1},dy[5]={0,1,-1,0,0};//偏移量
void turn(int x,int y){//按下开关
for(int i=0;i<5;i++){
if(x+dx[i]<5&&x+dx[i]>=0&&y+dy[i]<5&&y+dy[i]>=0){
g[x+dx[i]][y+dy[i]]=g[x+dx[i]][y+dy[i]]?0:1;
}
}
}
int main(){
int n;
cin>>n;
while(n--){
//输入,注意输入的是字符,这处理成数字(我做的时候做半天,发现是输入错了,真醉了)
for(int i=0;i<5;i++){
char str[6];
scanf("%s",str);
for(int j=0;j<5;j++){
bg[i][j]=str[j]-'0';
}
}
int res=10;//取任意一个大于6的数
for(int op=0;op<32;op++){//一行5个灯,每个灯有两种处理方法,按与不按,共32种组合情况,任意 一个32以内的数以五位二进制表示就是一种操作,0表示不按,1表示按
int cnt=0;
//将初始状态的数组复制到操作数组上
for(int i=0;i<5;i++)
for(int j=0;j<5;j++) g[i][j]=bg[i][j];
//将第一行的状态进行确定
for(int j=0;j<5;j++)
if(op>>j&1){//二进制位数从右到左
turn(0,j);
cnt++;
}
//基于以确定状态的第一行,从第一行开始,自上往下枚举四行
for(int i=0;i<4;i++)//i枚举的是最后一个已确定的那行,如果还有0的存在,只能通过下一行开关来 改变
for(int j=0;j<5;j++)
if(!g[i][j]){
turn(i+1,j);
cnt++;
}
//遍历最后一行看是否都为1
int j=0;
for(;j<5;j++){
if(!g[4][j]) break;
}
if(j>=5) res=min(res,cnt);
}
cout<<(res<=6?res:-1)<<endl;
}
return 0;
}
时间复杂度:500*32*25*5 (500个测试点,32个初始状态,25个开关,每次按开关影响五个开关)
刷题:
题目:砖块(一维递推)
n 个砖块排成一排,从左到右编号依次为 1∼n。
每个砖块要么是黑色的,要么是白色的。
现在你可以进行以下操作若干次(可以是 0 次):
选择两个相邻的砖块,反转它们的颜色。(黑变白,白变黑)
你的目标是通过不超过 3n 次操作,将所有砖块的颜色变得一致。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含一个整数 n。
第二行包含一个长度为 n 的字符串 s。其中的每个字符都是 W 或 B,如果第 i 个字符是 W,则表示第 i 号砖块是白色的,如果第 i 个字符是 B,则表示第 i 个砖块是黑色的。
输出格式
每组数据,如果无解则输出一行 −1。
否则,首先输出一行 k,表示需要的操作次数。
如果 k>0,则还需再输出一行 k 个整数,p1,p2,…,pk。其中 pi 表示第 i 次操作,选中的砖块为 pi 和 pi+1 号砖块。
如果方案不唯一,则输出任意合理方案即可。
数据范围
1≤T≤10,
2≤n≤200。
输入样例:
4
8
BWWWWWWB
4
BWBB
5
WWWWW
3
BWB
输出样例:
3
6 2 4
-1
0
2
2 1
代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const int N=210;
char str[N];
int main(){//找到其有两种性质:非黑即白,只能枚举n-1次.答案只有三种,全白,全黑和无解.每块与操作次数与顺序无关,只看有没有操作
int T;
cin>>T;
while(T--){
int n;
string str,strb,strw;
cin>>n>>str;
strb=str;
strw=str;
vector<int> locb;
vector<int> locw;
int cntb=0;
//枚举两次,第一块只能被第一次操作决定(还是包装成函数比较好,这样写太复杂,容易出错)
for(int i=0;i<n-1;i++){//看能否成为黑块
if(strb[i]=='W'){
strb[i]='B';
if(strb[i+1]=='W') strb[i+1]='B';
else strb[i+1]='W';
locb.push_back(i+1);
cntb++;
}
else continue;
}
if(strb[n-1]=='B'){
cout<<cntb<<endl;
if(cntb>0){
for(auto c:locb) cout<<c<<' ';
cout<<endl;
}
continue;
}
else{
int cntw=0;
for(int i=0;i<n-1;i++){//看能否成为白块
if(strw[i]=='B'){
strw[i]='W';
if(strw[i+1]=='B') strw[i+1]='W';
else strw[i+1]='B';
locw.push_back(i+1);
cntw++;
}
else continue;
}
if(strw[n-1]=='W'){
cout<<cntw<<endl;
if(cntw>0){
for(auto c:locw) cout<<c<<' ';
cout<<endl;
}
continue ;
}
else cout<<-1<<endl;
}
}
return 0;
}