描述
题解
个人感觉,这个应该算是数位 dp。
先通过处理输入数据获取一个 state[],表示每相邻两项之间的大小关系,state[i] = 0,表示无特别关系,state[i] = 1 表示第 i 项小于第 i - 1 项,state[i] = 2 表示第 i 项大于第 i - 1 项。
接着搞一个 dp[i][j] 表示由前 i 个数组成的序列且第 i 位为 j 的合法情况数。在规划的过程中,针对不同的 state[i],对应不同的状态转移(具体转移方程看代码吧),这里涉及到一个最后一位数 j 插入序列的思维,可以看做把前边的每一种排列中大于等于 j 的数 ++,也就可以达到空出 j 这个数将其插入的效果。
在这里引入一个 sum[j],表示为前一轮状态下,最后一位小于等于 j 的情况的和。也就是说,当规划到第 i 位时,sum[j] 表示前 i - 1 位数组成的序列的合法情况的 dp[i - 1][j] 的前缀和。这是为了降低复杂度,将三层循环降低为两层,复杂度从 O(n^3) 降低为 O(n^2)。
最后还使用了一个数据结构的优化——滚动数组,降低了内存的消耗,直接将二维数组降低为一维,原本打算将第一维减小到2,构建一个经典的滚动数组,可是后来发现由于状态转移方程比较特殊,根本没有直接用到过前一轮的状态,而是完完全全依赖于 sum[j],所以直接搞成一个一维的 dp[j] 即可了。
就这样子,没毛病了。
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int MAXN = 5e3 + 10;
const int MOD = 1e9 + 7;
int N, K, L;
int state[MAXN], sum[MAXN];
int dp[MAXN];
int main()
{
scanf("%d%d%d", &N, &K, &L);
int a;
for (int i = 0; i < K; i++)
{
scanf("%d", &a);
state[++a] = 1;
state[a + 1] = 2;
}
for (int i = 0; i < L; i++)
{
scanf("%d", &a);
state[++a] = 2;
state[a + 1] = 1;
}
sum[0] = 0;
dp[1] = sum[1] = 1;
for (int i = 2; i <= N; i++)
{
for (int j = 1; j <= i; j++)
{
if (state[i] == 0)
{
dp[j] = sum[i - 1];
}
else if (state[i] == 1)
{
dp[j] = ((sum[i - 1] - sum[j - 1]) % MOD + MOD) % MOD;
}
else
{
dp[j] = sum[j - 1];
}
}
for (int j = 1; j <= i; j++)
{
sum[j] = (sum[j - 1] + dp[j]) % MOD;
}
}
printf("%d\n", sum[N]);
return 0;
}