基本思想
最常见的基础的递推就是斐波拉契数列,并且在dp里面经常用到递推思想,因为很多时候是根据状态转移方程求解下一个状态,基本就算是递推得到的下一个状态
递推字面意思,其实就是按照某种顺序一步一步地往后推,直到得到答案
也可以理解为先将前面的状态确定了,然后再往后求后面的可能的状态,探究前面确定的状态对后面的所有状态的影响或者探究前面的状态如何由后面的操作得到
典型例题
砖块
题目链接:https://www.acwing.com/problem/content/3780/
分析思路:这道题就是很典型地从前往后对序列进行推导的例子,不断地使得前面已经处理过的序列达到结果的要求,然后再对后面的序列进行处理。
分别考虑将全部砖块变为白色或者黑色的情况,假如我们先讨论将所有砖块变为白色,从左往右遍历砖块序列,如果遇见白色的表示满足结果要求,遇见黑色的就对当前第i个和第i+1个砖块翻转,通过这样的操作如果最后一个成功地是白色,就满足要求,否则该序列就无法全部翻转为白色。
翻转为白色和翻转为黑色的情况得到的结果取最小值即可,并且还要判断一下是否满足题目翻转次数在3*n次以内。
AC代码:
#include<iostream>
#include<vector>
using namespace std;
int t,n;
string sa;
vector<int>ans;
void output()
{
int len =ans.size();
cout<<len<<endl;
cout<<ans[0];
for(int i=1;i<len;i++)
{
cout<<" "<<ans[i];
}
cout<<endl;
}
//将所有块全部换成a色,遇见b就交换b和b后面那个
bool jude(char a,char b)
{
int i=1;
for(;i<n;i++)
{
if(sa[i]==b)
{
sa[i]=a;
if(sa[i+1]=='B') sa[i+1]='W';
else sa[i+1]='B';
ans.push_back(i);
}
cout<<sa<<endl;
}
if(sa[i]==b||ans.size()>3*n) return false;//最后还剩下一个是b色说明不能全部换成白色,最后剩下的也是a色就成立
else return true;
}
int main()
{
cin>>t;
while(t--)
{
cin>>n;
cin>>sa;
sa=" "+sa; //下标从1开始
bool tong=true;
for(int i=1;i<=n;i++)
{
if(sa[i]!=sa[1])
{
tong =false; break;
}
}
if(tong)
{
cout<<0<<endl; continue;
}
//先考虑全部换为白色的情况,遇见一个黑色就让它和它后面那个换
if(jude('W','B'))
{
output();
}
else if(jude('B','W')) //再考虑全部换为黑色的情况
{
output();
}
else cout<<-1<<endl;
ans.clear();
}
return 0;
}
翻硬币
题目链接:这道题是蓝桥杯的一个题 2013 省赛 翻硬币
分析思路:这道题跟上一个砖块是一样的思路,只不过是不断地往结果序列靠,每到达一个位置判断该位置处的字符是否是结果序列所需要的,如果不是就进行翻转,在每翻转了一个硬币后进行一下判断目前的序列是否是题目要求的结果序列,是就结束翻转就行。
AC代码:
#include <iostream>
using namespace std;
string a,b;
void fan(int i)
{
if(a[i]=='*') a[i]='o';
else a[i]='*';
}
int main()
{
cin>>a>>b;
int lena=a.length();
int sum=0;
for(int i=0;i<lena-1;i++)
{
if(a[i]!=b[i]) //翻转连续的两个
{
fan(i); fan(i+1);
sum++;
}
if(a==b) break;
}
cout<<sum<<endl;
return 0;
}
费解的开关
题目链接:https://www.acwing.com/problem/content/97/
分析思路:
这个题用的思路主要是探究前面一行的序列状态确定后,对后面一行的序列的影响,也就是我们先确定对第一行的所有元素的操作,得到第一行所有的状态,然后探究如果想让当前状态下第一行的所有灯都亮,第二行需要作怎样的操作,如果deng[i-1][j] = 0,表示该灯没亮,就需要对deng[i][j] 进行操作(状态改变),这样才能使得deng[i-1][j] = 1,能看出来,如果第一行的状态确定了,后面所有行的状态通过这种方式也就能确定,如果操作结束后最后一行的灯有没亮的,那就说明这种状态是无法得到答案的,如果全都亮了就更新答案就行。
AC代码:
#include<iostream>
#include<string.h>
using namespace std;
int n;
int deng[6][6],temp[6][6];
void fan(int x,int y)
{
if(deng[x][y]) deng[x][y]=0;
else deng[x][y]=1;
}
//将以(x,y)为中心的十字架进行翻转
void turn(int x,int y)
{
fan(x,y);
if(x>0) fan(x-1,y);
if(x+1<5) fan(x+1,y);
if(y>0) fan(x,y-1);
if(y+1<5) fan(x,y+1);
}
int main()
{
cin>>n;
while(n--)
{
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
char t;cin>>t;
deng[i][j]=t-'0';
}
}
int ans=0x3f3f3f3f;
//枚举第一行的按法
for(int op=0;op<32;op++) //五位二进制中为1的表示按,为0的表示不按,00000表示全不按,11111表示全按
{
memcpy(temp,deng,sizeof deng); //暂时保存一下,方便下一个状态时使用
int c=0;
//确定第一行的状态
for(int i=0;i<5;i++) //通过左移来确定五位二进制中每个位置的元素是1还是0
{
if((op>>i)&1) //左移后用最低位跟1做与运算
{
turn(0,i); c++;
}
}
// cout<<"确定了第一行的状态"<<endl;
// output();
//根据第一行的状态,对剩下的2,3,4,5行进行操作
for(int i=1;i<5;i++)
{
for(int j=0;j<5;j++)
{
if(!deng[i-1][j]) //上一行是0关着的状态,通过这一行来点亮它
{
turn(i,j); c++;
// output();
}
}
}
bool f=true;
for(int j=0;j<5;j++)
{
if(!deng[4][j]) f=false; //最后一行存在1表示该方案行不通
}
if(f&&c<=6) ans=min(ans,c);
// cout<<"翻转完了后"<<endl;
// output();
memcpy(deng,temp,sizeof temp);
}
if(ans==0x3f3f3f3f) cout<<-1<<endl;
else cout<<ans<<endl;
}
return 0;
}