【BZOJ4321】queue2【dp】
题目描述
(n) 个沙茶,被编号 (1~n)。排完队之后,每个沙茶希望,自己的相邻的两人只要无一个人的编号和自己的编号相差为 (1)((+1) 或(-1))就行;
现在想知道,存在多少方案满足沙茶们如此不苛刻的条件。
输入格式
只有一行且为用空格隔开的一个正整数 (N)。
输出格式
一个非负整数,表示方案数对 7777777
取模。
样例
样例输入
4
样例输出
2
样例解释
有两种方案 (2 4 1 3) 和 (3 1 4 2)
数据范围与提示
对于(30\%)的数据满足(Nleqslant 20)
对于(100\%)的数据满足 ;(1leqslant N leqslant 10^4)
分析
这种带有这种限制的 (dp) 题,我们通常的状态定义就是前 (i) 以 (j) 结尾的方案数是多少,但是这个题并不能这样写,因为下一个放置的人的编号会影响当前放置编号后的状态,所以我们换一个思路:
定义 (f[i][j][0/1]) 表示向序列中插入元素 (i) ,当前有 (j) 对两两之间相差一的人, (0/1)表示当前插入的 (i) 是否和 (i-1)挨着,那么由于第三维的不确定性,我们需要考虑两个状态转移。
首先看 (f[i][j][1]) 的转移,因为 (f[i][j][1]) 表示当前插入 (i) 后有 (j) 对编号相差一的,因为这里表示插入后 (i) 和 (i-1) 是相邻的,所以只可能从 (j-1) 对或者 (j) 对转移来。当 (i-1) 与 (i-2) 相邻的时候,也就是第三维是 (1) ,这时候会有两种能转移来的方案:
一个是从 (f[i-1][j][1]) 转移来,这时候我们只需要把 (i)插入到 (i-1) 与 (i-2) 中间就行。
还有一个是从 (f[i-1][j-1][1]) 转移来,我们只需要在 (i-1) 不和 (i-2) 挨着的那一侧插入 (i) 即可。
然后考虑第三维是 (0) ,这时候只可能从 (f[i-1][j-1][0]) 转移来,因为要求 (i) 和 (i-1) 必须相邻,这时候 (i-1) 与 (i-2) 是不相邻的,所以我们有两种插入方法,在 (i-1) 左侧和右侧。那么这个转移方程就是:
然后就是 (f[i][j][0]) 的转移,当前如果 (i) 和 (i-1) 不相邻,那么只可能让 (j) 不变或者减小,所以上一个的 (j) 只可能是 (j) 和 (j+1) 。
首先考虑从 (f[i-1][j+1][0]) 的转移 这时候只需要在 (j+1) 段中任意插入个 (i) 就成了 (f[i][j][0]) ,而这时候有 (j+1) 种,也就是 (f[i-1][j+1][0] imes (j+1))。
然后是 (f[i-1][j+1][1]) 这时候不能插入到 (i-1) 和 (i-2) 的这一段中,所以情况有 (j) 种,也就是 (f[i-1][j+1][1] imes j)
然后分析上一次也有 (j) 对的情况,如果挨着,即(f[i-1][j][1]),那么可以在除了这些对之外的位置放,一共有 (i-j-2) 种,因为也可以在 (i-1) 和 (i-2) 之间放,所以有 (i-j-1) 种,不挨着的情况与这个类似,只不过只能在这些对之外的地方放,那么状态转移方程就是:
代码
#include<cstdio>
using namespace std;
#define ll long long
const int maxn = 1000+10;
const int mod = 7777777;
ll f[maxn][maxn][2];
int main(){
int n;
scanf("%d",&n);
f[2][1][1] = 2;
for(int i=3;i<=n;++i){
for(int j=0;j<i;++j){
f[i][j][0] = (f[i-1][j+1][0] * (j+1) + f[i-1][j+1][1] * j + f[i-1][j][1] * (i-j-1) + f[i-1][j][0] * (i-j-2))%mod;
if(j)f[i][j][1] = (f[i-1][j][1] + 2 * f[i-1][j-1][0] + f[i-1][j-1][1])%mod;
}
}
printf("%lld
",f[n][0][0]);
return 0;
}