文章目录
递推
递推算法是一种简单的算法,即通过已知条件,利用特定关系得出中间推论,直至得到结果的算法。递推算法分为顺推和逆推两种。
1、扫雷MINE
NC20241 扫雷MINE
题目描述
相信大家都玩过扫雷的游戏。那是在一个n*m的矩阵里面有一些雷,要你根据一些信息找出雷来。
万圣节到了 ,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则和扫雷一样,如果某个格子没有雷,那么它里面的数字 表示和它8连通的格子里面雷的数目。
现在棋盘是n×2的,第一列里面某些格子是雷,而第二列没有雷,如下图: 由于第一列的雷可能有多种方案满足第二列的数的限制,你的任务即根据第二列的信息确定第一列雷有多少种摆放方案。
输入描述:
第一行为N,第二行有N个数,依次为第二列的格子中的数。(1 ≤ N ≤ 10000)
输出描述:
一个数,即第一列中雷的摆放方案数。
示例1
输入
2
1 1
输出
2
方法一 动态规划
解题思路:
1、用二进制代表附近三位的状态1代表有雷,0代表没有,那么状态一共有8个,000、001、010、011、100、101、110、111,下面解释样例
2、初始地,a[2]={1,1},i=0时,a[i]=1,那么状态dp[0][001]=1,dp[0][010]=1,i=1时,a[i]=1,那么状态dp[1][010]+=dp[0][001],dp[1][100]+=dp[0][010],仔细分析转移的关系即可,例如100可以由010以及110转移得到,011可以由001以及101转移得到,即前两位与前一个状态的后两位相等
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
int a[10010]={
0};
int dp[10010][8]={
0};
cin>>n;
int ans=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(i==1){
//第一个需要初始化
if(a[i]==0){
dp[i][0]++;
}else if(a[i]==1){
dp[i][1]++;
dp[i][2]++;
}else if(a[i]==2){
dp[i][3]++;
}
}else{
//状态转移
if(a[i]==0){
dp[i][0]+=dp[i-1][0]+dp[i-1][4];
}else if(a[i]==1){
dp[i][1]+=dp[i-1][0]+dp[i-1][4];
dp[i][2]+=dp[i-1][1]+dp[i-1][5];
dp[i][4]+=dp[i-1][2]+dp[i-1][6];
}else if(a[i]==2){
dp[i][3]+=dp[i-1][1]+dp[i-1][5];
dp[i][5]+=dp[i-1][2]+dp[i-1][6];
dp[i][6]+=dp[i-1][3]+dp[i-1][7];
}else if(a[i]==3){
dp[i][7]+=dp[i-1][3]+dp[i-1][7];
}
}
}
ans+=dp[n][0]+dp[n][2]+dp[n][4]+dp[n][6];//因为最后一个后一位必定是0
cout<<ans<<endl;
}
方法二 递推
解题思路:
我们换种思路,其实前两个状态固定了的话,后面的状态其实已近被固定了,所以我们只需要枚举前两个的状态位,递推出后面的状态,如果可以的话就计入答案,也就是说,答案最多有4种方案
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
int a[10010]={
0};
int dp[10010]={
0};
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
int ans=0;
for(int i=0;i<4;i++){
//枚举四个状态
dp[0]=i&1;//初始化
dp[1]=(i>>1)&1;
if(dp[0]+dp[1]!=a[0]){
continue;
}
for(int j=1;j<n;j++){
dp[j+1]=a[j]-dp[j-1]-dp[j];
if(dp[j+1]<0||dp[j+1]>1){
//下一位只能是1或者0
break;
}
if(j==n-1&&dp[n]==0){
//符合条件,最后一位必须是0
ans++;
}
}
}
cout<<ans<<endl;
}
2、牛可乐的翻转游戏
NC235250 牛可乐的翻转游戏
题目描述
牛可乐发明了一种新型的翻转游戏!
在一个有 nn 行 mm 列的棋盘上,每个格子摆放有一枚棋子,每一枚棋子的颜色要么是黑色,要么是白色。每次操作牛可乐可以选择一枚棋子,将它的颜色翻转(黑变白,白变黑),同时将这枚棋子上下左右相邻的四枚棋子的颜色翻转(如果对应位置有棋子的话)。
牛可乐想请你帮他判断一下,能否通过多次操作将所有棋子都变成黑色或者白色?如果可以,最小操作次数又是多少呢?
输入描述:
第一行两个整数 n,m(1≤n≤100,1≤m≤10),代表棋盘的行数和列数。
之后 n 行,每行 m 个数字,第 i 个数字如果为 0 ,代表对应位置的棋子为白色,如果为 1 则为黑色。
输出描述:
如果无法将所有棋子变成一个颜色,输出 “Impossible”(不含引号),否则输出变成一个颜色的最小操作次数。
示例1
输入
4 4
1001
1101
1001
1000
输出
4
解题思路:
如果第一行的翻转已经被确定了,那么第二行的操作也会被确定,因为第二行的翻转需要把第一行全部翻转成1或是0,所以我们只需要枚举第一行的翻转,判断该翻转是否满足题意即可,m<=10,那么最多有210种翻转方案
代码:
#include<bits/stdc++.h>
using namespace std;
int getch(int val){
int ans=0;
while(val){
ans++;
val&=(val-1);
}
return ans;
}
int main(){
int n,m;
int a[105]={
0};
int dp1[105]={
0};
int dp2[105]={
0};
int ans=0x3f3f3f3f;
cin>>n>>m;
for(int i=0;i<n;i++){
string b;
cin>>b;
for(int j=0;j<m;j++){
a[i]*=2;
if(b[j]=='1'){
a[i]+=1;
}
}
}
for(int i=0;i<(1<<m);i++){
//(1<<m)个状态
int sum1=0;
int sum2=0;
sum1+=getch(i);
sum2+=getch(i);
dp1[0]=a[0]^i^(i>>1)^((i<<1)&((1<<m)-1));//全部变1
dp1[1]=a[1]^i;
dp2[0]=a[0]^i^(i>>1)^((i<<1)&((1<<m)-1));//全部变0
dp2[1]=a[1]^i;
for(int j=1;j<n;j++){
//后面的翻转已近被固定了
int ch1=dp1[j-1]^((1<<m)-1);
int ch2=dp2[j-1];
sum1+=getch(ch1);
sum2+=getch(ch2);
dp1[j]=dp1[j]^ch1^(ch1>>1)^((ch1<<1)&((1<<m)-1));
dp1[j+1]=a[j+1]^ch1;
dp2[j]=dp2[j]^ch2^(ch2>>1)^((ch2<<1)&((1<<m)-1));
dp2[j+1]=a[j+1]^ch2;
}
if(dp1[n-1]==(1<<m)-1){
//最后一排全部是1
ans=min(ans,sum1);
}
if(dp2[n-1]==0){
//最后一排全部是0
ans=min(ans,sum2);
}
}
if(ans==0x3f3f3f3f){
cout<<"Impossible"<<endl;
}else{
cout<<ans<<endl;
}
}
是不是很简单呢?
刚接触肯定会觉得难,多些做题多些用,熟悉了就容易了,兄弟萌,加油!!!
文章尚有不足,欢迎大牛们指正
感谢观看,点个赞吧