【BZOJ4321】queue2【dp】
【BZOJ4321】queue2【dp】
题目描述
\(n\) 个沙茶,被编号 \(1~n\)。排完队之后,每个沙茶希望,自己的相邻的两人只要无一个人的编号和自己的编号相差为 \(1\)(\(+1\) 或\(-1\))就行;
现在想知道,存在多少方案满足沙茶们如此不苛刻的条件。
输入格式
只有一行且为用空格隔开的一个正整数 \(N\)。
输出格式
一个非负整数,表示方案数对 7777777
取模。
样例
样例输入
4
样例输出
2
样例解释
有两种方案 \(2\ 4\ 1\ 3\) 和 \(3\ 1\ 4\ 2\)
数据范围与提示
对于\(30\%\)的数据满足\(N\leqslant 20\)
对于\(100\%\)的数据满足 ;\(1\leqslant 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] \times (j+1)\)。
然后是 \(f[i-1][j+1][1]\) 这时候不能插入到 \(i-1\) 和 \(i-2\) 的这一段中,所以情况有 \(j\) 种,也就是 \(f[i-1][j+1][1] \times 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\n",f[n][0][0]);
return 0;
}