【题解】P2467 [SDOI2010] 地精部落

· · 题解

讨论板中提到了一种冷门 DP trick——连续段 DP,看了一下本题还没有相关的题解,写一篇权当补充。

感谢 @Richard_Whr 提到这种 DP。
感谢 @Charlie_ljk 提供的代码参考。

连续段 DP

这是一种 DP 思想,一般作用在有关排列的 DP 题上。常常会在考虑前 i 个元素的基础上记录现在存在 j 个合法的连续段,每次转移考虑合并连续段并使其保持合法状态,最终查询只有 1 个连续段时的 DP 值即答案。

本题

状态设计

状态转移方程

时刻记得:

  1. 目前插入的节点 i 是最高的。
  2. 每个连续段都以山谷开头结尾。

    f[i][j][0] 的转移

如果我们不合并原有连续段,则上图红色标识的位置都可以加入一个新的连续段,即 j\leftarrow j-1 的过程。

如果我们趁着 i 的加入按照上面的红框选一段合并,则会减少一个连续段,即 j\leftarrow j+1 的过程。

所以简单转移方程为:

f[i][j][0]=f[i-1][j-1][0]\times j+f[i-1][j+1][0]\times j

f[i][j][1] 的转移

仍然先考虑不合并的情况,因为其尾部高度已经被钦定,所以将 i 作为一个独立段插入尾部是不行的,如上图尾部被钦定后是山峰或是山谷的状态是无关的,所以我们不必记录这个钦定的高度到底是多少。

对于利用 i 合并的情况也是一样的。

如果从一个没有钦定过尾部高度的地方转移过来,则也有两种情况,一种是 i 单独成段插入,一种是 i“黏附”在最后一段的尾端,后者也恰好能完美统计上以山峰结尾,这也能证明只要用类似的处理方法就能做到不遗漏。

于是:

f[i][j][1]=f[i-1][j-1][1]\times(j-1)+f[i-1][j+1][1]\times j+f[i-1][j-1][0]+f[i-1][j][0])

f[i][j][2] 的转移

同上 f[i][j][1]

f[i][j][3] 的转移

想必有上述的分析,这个转移也差不多能解读了:

f[i][j][3]=f[i-1][j-1][3]\times(j-2)+f[i-1][j+1][3]\times j+f[i-1][j-1][2]+f[i-1][j][2]+f[i-1][j-1][1]+f[i-1][j][1]

初态

### 终态 当答案统计完毕时,头尾是多少肯定是一定的,~~不会处于钦定和未钦定的量子叠加态~~,故终态为考虑插入了前 $n$ 个数,只有 $1$ 个连续段且头尾都钦定的 $3$ 状态即 $f[n][1][3]$。 --- 注意这种做法需要 $f$ 数组进行滚动优化空间。 注意取模。 ## AC 代码 ```cpp #include <iostream> #include <cstdio> #include <cctype> using namespace std; inline void read(int &x) { char c=getchar();x=0; while(!isdigit(c)) c=getchar(); while(isdigit(c)) x=x*10-48+c,c=getchar(); } int n,p,f[2][4205][4]; int main() { read(n);read(p); f[1][1][0]=f[1][1][1]=f[1][1][2]=1; for(int i=2;i<=n;i++) for(int j=1;j<=i;j++) { f[i&1][j][0]=(1ll*f[(i-1)&1][j-1][0]*j%p+1ll*f[(i-1)&1][j+1][0]*j%p)%p; f[i&1][j][1]=(1ll*f[(i-1)&1][j-1][1]*(j-1)%p+1ll*f[(i-1)&1][j+1][1]*j%p+f[(i-1)&1][j-1][0]+f[(i-1)&1][j][0])%p; f[i&1][j][2]=(1ll*f[(i-1)&1][j-1][2]*(j-1)%p+1ll*f[(i-1)&1][j+1][2]*j%p+f[(i-1)&1][j-1][0]+f[(i-1)&1][j][0])%p; f[i&1][j][3]=(1ll*f[(i-1)&1][j-1][3]*(j-2)%p+1ll*f[(i-1)&1][j+1][3]*j%p+f[(i-1)&1][j-1][2]+f[(i-1)&1][j][2]+f[(i-1)&1][j-1][1]+f[(i-1)&1][j][1])%p; } printf("%d",f[n&1][1][3]); return 0; } ```