题意:
窗口可放n面红蓝白三种旗,规定同色不相邻,蓝在红白之间。共有多少种放置方法。
思路:
设dp[i][j]表示有i面旗,第i面旗填颜色j(j=01表示红白)时的总数,第i面填j色时,i-1可以填1-j(红白相间)或者蓝色,两种填法的计算:
(1)填1-j时有dp[i-1][1-j]种
(2)填蓝色时i-2和i要填红白色才能将i-1的蓝色包围,即i-2要填1-j,共dp[i-2][1-j]种
所以,状态转移方程为dp[i][j]=dp[i-1][1-j]+dp[i-2][1-j]。答案是dp[n][1]+dp[n][0]。
空间优化:
每一轮循环都要计算
dp[i][0] = dp[i-1][1]+dp[i-2][1]
dp[i][1] = dp[i-1][0]+dp[i-2][0]
两式相加得:(dp[i][0] + dp[i][1]) = (dp[i-1][0]+ dp[i-1][1]) + (dp[i-2][0]+dp[i-2][1])
两两结构相同,去掉第二维,dp[i] 表示前i面旗的放置方法总数,第i面旗可以填红白色。
由上面的方程得:dp[i] = dp[i-1] + dp[i-2]
直接给dp[i]分类很快,就是凑题解字数时,发现这种降维的方法,和背包的降维不一样。
状态转移分析:
第i面红色,i-1面白色:dp[i-1]是i-1填红白色的总数,红白各一半,白色有dp[i-1]/2种
第i面红色,i-1面蓝色:i-2必须是白色,则有dp[i-2]/2种
第i面白色,i-1面红色:同第一种,有dp[i-1]/2种
第i面白色,i-1面蓝色:同第二种,有dp[i-2]/2种
四个加起来得到dp[i]。
初始化:
dp[i]依赖于前两个,所以至少要提供两个连续的dp[i],可以是dp[0] dp[1]或dp[1] dp[2]
算法步骤:
步骤1:初始化为dp[i]=0,dp[1]=2
步骤2:递推求dp[i],答案是dp[n]
算法复杂度:
初始化和递推都是一个循环,时间复杂度O(n),空间复杂度O(n)
代码:
#include <cstdio> const int MAX_N = 46; long long int cnt[MAX_N]; int main() { int n; scanf("%d", &n); cnt[1] = cnt[2] = 2; for (int i = 3; i <= n; ++i) cnt[i] = cnt[i - 1] + cnt[i - 2]; printf("%lld ", cnt[n]); return 0; }
突然想到前面那个完全背包的输入,第i个物品用完就不用了,不用用数组保存。
这道题只要维护三个值dp[i] dp[i-1] dp[i-2]就可以了,也就是
c = a + b; b = a; a = c; -> a+b未更新 就是 a = a + b; b = a – b; // (a旧值+b)-b
维护两个变量就行了OoO ,循环用while连for里面的局部变量也省了,代码好短
算法步骤见代码,main函数四行,一行输入,一行计算,一行输出,一行return 0
代码:
#include <cstdio> int n; long long a=2,b=0; // a=[1] b=a[0] int main() { scanf("%d", &n); while(--n) a = a + b , b = a - b; printf("%lld ", a); return 0; }