大盗阿福
题目链接:大盗阿福
分析:看到这个题目是比较简单的,我们直接写就能写出来。
#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int n;
int a[N];
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
}
for(int i=1;i<=n;i++){
f[i]=max(f[max(0,i-2)]+a[i],f[i-1]);//从偷这一家和不偷这一家的情况中选出最大值,如果不偷这一家,可以从f[i-2]+a[i]的值转移而来,f[i-2]保证了没偷第i-1家,而且记录的是最大值,否则只能从f[i-1]直接继承,记录的也是最大值
}
printf("%d\n",f[n]);
}
return 0;
}
我们很容易从直觉上感觉到以上代码的正确性,不过我们可以多加一位状态让上述状态更清晰。
我们用dp[i][0]
表示从第1家到第i家并且不偷第i家的所有情况中偷盗的最大值,dp[i][1]
表示从第1家到第i家并且偷第i家的所有情况中偷盗的最大值,那么状态转移就更加清晰了:dp[i][0]=max(dp[i-1][0],dp[i-1][1]
如果不偷当前这一家,直接从偷与不偷上一家的状态中继承最大值,dp[i][1]=dp[i-1][0]+a[i]
,偷这一家的话,就只能从不偷上一家的状态转移而来。
这么看,我们的状态好像还多了一维,是更麻烦了吗?其实更清楚了,对于更多状态的情况,我们用这种方法也会更清楚。
股票买卖4
题目链接:股票买卖4
分析:力扣股票买卖系列,也算是比较有意思的题目了,我们用dp[i][j][0]表示考虑前i天、买卖了j次(或正在第j次),手里没有股票的最大收益,dp[i][j][1]表示考虑前i天,买卖了j次,手里有股票的最大收益,注意:一次买+一次卖才算一次买卖,我们用w[i]表示第i天股票的价格,然后我们初始化状态,再推导一下状态转移方程。初始化状态:dp[i][0][0]
表示前i天无交易无股票,那么收益为0,而对于dp[0][i][0](i>=1&&i<=m)
和dp[0][i][1](i>=1&&i<=m)
都是不可能出现的情况,因为前0天是不可能有交易的(注意可以在同一天买入并卖出),这些不可能的情况我们只需标记成一个非常小的负数,就不会转移了(会被大于等于0的数淘汰,或者只能转移到其它不可能的状态)。
状态转移:dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+w[i])
对dp[i][j][0]
,要么继承上一天无股票的状态不变,要么在上一天还有股票的情况下卖出(注意,这里不是j-1,因为当股票买入后就算一次交易开始了,卖出后才算结束,这里卖出虽然结束了,但并未开启一次新的交易)
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]+w[i])
对dp[i][j][1]
,要么继承上一天有的股票,要么上一天只完成了j-1次交易且手中无股票,在今日买入股票,注意买入股票要扣钱。
最后答案的状态一定是考虑了所有天数并且手中无股票的情况,但是交易了几次就不一定了,因此从这些情况中找出最大的就是答案。
代码实现:
#include<iostream>
#include<cstring>
using namespace std;
const int M=110,N=1e5+10;
int w[N];
int dp[N][M][2];
int main(){
int n,k;
scanf("%d%d",&n,&k);
memset(dp,-0x3f,sizeof dp);
for(int i=0;i<=n;i++) dp[i][0][0]=0;
//for (int i = 1; i <= m; i++) f[0][i][0] = f[0][i][1] = -INF;这样也行
//可以滚动数组优化为1维
for(int i=1;i<=n;i++) scanf("%d",w+i);
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+w[i]);
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-w[i]);
}
}
int res=0;
for(int i=0;i<=k;i++){
res=max(res,dp[n][i][0]);
}
printf("%d\n",res);
return 0;
}
股票买卖5
题目链接:股票买卖5
分析:这个题也很容易,而且没有交易次数限制,直接用f[i][0]
表示手中没有股票且可以买入股票的状态,f[i][1]
表示手中有股票的状态,f[i][2]
表示处于冷却期的状态。那么很明显,我们按照这样转移状态,就能求出最终答案了。
代码实现:
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int f[N][3];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i);
f[0][1]=f[0][2]=-1e9;//只考虑前0天不可能有股票或者处于冷却期
for(int i=1;i<=n;i++){
f[i][0]=max(f[i-1][0],f[i-1][2]);//要么上一天也是同一状态,要么上一天处于冷却期
f[i][1]=max(f[i-1][1],f[i-1][0]-a[i]);//上一天就持股或者今天买入
f[i][2]=f[i-1][1]+a[i];//处于冷却期说明上一天才卖掉彩票
}
printf("%d\n",max(f[n][0],f[n][2]));//取到答案时肯定没有股票,从剩下两者选一个大的
return 0;
}
设计密码
题目链接:设计密码
分析:这题真是一看就恶心的那种,题目要求S中不含子串T,我们可能联想到KMP算法,T字符串与S字符串匹配不到T的最后一位时,说明S中不含T,因此我们可以以S字符串和T字符串中匹配的位置(T中的位置)划分状态,然后再求方案数。然后S字符串的每一位可以是26个字母,因此我们枚举每一个字母再求匹配位置,再累加方案数。
代码实现:
#include<iostream>
#include<cstring>
using namespace std;
int n;
const int N=55,mod=1e9+7;
int dp[N][N];
char s[N];
int ne[N];
int main(){
cin>>n>>s+1;
int len=strlen(s+1);
for(int i=2,j=0;i<=len;i++){//kmp求ne数组
while(j&&s[j+1]!=s[i]) j=ne[j];
if(s[j+1]==s[i]) j++;
ne[i]=j;
}
dp[0][0]=1;//已经匹配了0位,且匹配的子串的位置是0时的方案数为1
for(int i=0;i<n;i++){//枚举第0位~第n-1位密码(一共n位)
for(int j=0;j<len;j++){//枚举第i位密码匹配到的位置,不能匹配到len
for(char k='a';k<='z';k++){//枚举第i+1位可能的字母
int u=j;
while(u&&s[u+1]!=k) u=ne[u];
if(s[u+1]==k) u++;//求出第i+1位的匹配位置
if(u<len) dp[i+1][u]=(dp[i+1][u]+dp[i][j])%mod;//如果len每匹配,就加上方案数
}
}
}
int res=0;
for(int i=0;i<len;i++) res=(res+dp[n][i])%mod;//把所有方案累加
printf("%d",res);//输出结果
return 0;
}
真的难这题。。。