E - 积木画(状态压缩DP)
1、问题
E - 积木画
2、分析
这道题很明显是一道DP题,而且是一个简化版的状态压缩DP。
(1)状态表示
f[i][j]f[i][j]f[i][j]表示前面i−1i-1i−1已经摆好,并且第iii列的状态是jjj的条件下,所有的摆法数量。·
为什么这里是i−1i-1i−1呢,请看下面的讲解。
(2)状态转移
在状态转移之前,我们先看如下规定:
很明显,f[i][j]f[i][j]f[i][j]与f[i+1][j]f[i+1][j]f[i+1][j]是两个截然不同的状态,那么当我们在第iii列放积木的时候,由于积木形状的特殊性,我们不难发现,第iii列放的积木会影响到第i+1i+1i+1列的状态,而正是这种影响在两个截然不同的状态之间搭建起了一个桥梁,即实现了状态的转移,因此我们接下来的讨论就是根据第iii列的不同的积木的摆法,去写转移方程。
在讲解之前,我们需要给各个状态做一下标记:
详细地状态规定如下图所示:
现在开始直接讨论iii和i+1i + 1i+1列的两种状态。
我们去枚举第iii列的所有状态,黑色表示iii列自带的状态,红色和橙色表示我们在第iii列所有能填的情况。所有的情况如下图所示:
将上图转化为状态转移方程:
f[i+1][0]=f[i][0]+f[i][3]f[i+1][1]=f[i][0]+f[i][2]f[i+1][2]=f[i][0]+f[i][1]f[i+1][3]=f[i][0]+f[i][1]+f[i][2]+f[i][3]f[i+1][0]=f[i][0]+f[i][3] \\f[i+1][1]=f[i][0]+f[i][2] \\f[i+1][2]=f[i][0]+f[i][1] \\f[i+1][3]=f[i][0]+f[i][1]+f[i][2]+f[i][3] f[i+1][0]=f[i][0]+f[i][3]f[i+1][1]=f[i][0]+f[i][2]f[i+1][2]=f[i][0]+f[i][1]f[i+1][3]=f[i][0]+f[i][1]+f[i][2]+f[i][3]
(3)初末状态
初始状态的话,我们只需要让f[1][0]=1f[1][0] = 1f[1][0]=1即可。即我们在第111列没有积木填充的方案数是111。为什么呢?
因为当前列的状态是由前一列的积木的放置状况决定的,而第000列不存在,所以没法放积木,所以第111列的状态只有000是合法的。
末状态即(f[n][3]+f[n][0])(f[n][3] +f[n][0])(f[n][3]+f[n][0])或者写作f[n+1][0]f[n+1][0]f[n+1][0]。
这里解释一下为什么前面要写成f[n][3]+f[n][0]f[n][3]+f[n][0]f[n][3]+f[n][0]?
因为如果第iii列是空的,所以我们可以自动填充一个竖着的积木即可。
(4)滚动数组优化
由于这道题的数据范围很大,我们的DP数组可能存不下,而我们发现我们每次存储都只用到了上一层的数据,对于上一层之前的数据其实是没有用的,也就是说我们只需要在两层之间不断地迭代滚动即可,无需开这么大的空间。
那么滚动的方法即我们将下标和1取&\&&运算即可。
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
int f[3][4];void solve()
{int n;cin >> n;f[1][0] = 1;for(int i = 1; i <= n; i ++ ){f[i + 1 & 1][0] = (f[i & 1][0] + f[i & 1][3]) % mod;f[i + 1 & 1][1] = (f[i & 1][0] + f[i & 1][2]) % mod;f[i + 1 & 1][2] = (f[i & 1][0] + f[i & 1][1]) % mod;f[i + 1 & 1][3] = (f[i & 1][0] + f[i & 1][1] + f[i & 1][2]) % mod;}cout << f[n + 1 & 1][0] << endl;
}
signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);solve();
}