P2467

· · 个人记录

[SDOI2010]地精部落

感觉并不是很好做的计数 DP,想了很长时间没有什么很简单的方法。。。

法一:定义 f(i,j) 表示序列中有 i 个数,第一个数为 j,且第一个数为山峰的方案数。

那么我们知道,对于 f(i,j),它其实还表示以 i-j+1 为高度的山谷作为开头,序列中同样有 i 个数,此时的序列方案数。原因就是我们可以将整个序列翻转(盗图):

那么我们可以得知最后的答案就是 \sum_{i=1}^n 2f(n,i)

那么转移怎么办:

f(i,j)=f(i-1,i-j+1)+f(i,j-1)

这里的 f(i-1,i-j+1) 就表示以 j-1 为开头的山谷,然后再开头拼上 j。这里的 f(i,j-1) 就表示我们不以 j-1 为开头谷底时的情况(原理是交换 f(i,j-1) 中的 j-1j 的位置,序列仍然合法,但是 j 处在开头且为山峰,并且紧接着它后面的山谷不会是 j-1)。

于是这样就可以不重不漏的统计了。不过并没有那么的直觉。

时间复杂度 O(n^2)

法二:一维的想法。稍微直觉一些。

那么 $f(i)=\sum_{j\in[1,i],j=2k+1}f(j-1)f(i-j)C_{i-1}^{j-1}$。 这里的意思就是说,枚举 $i-1$ 个数中的 $i$ 个位置,考虑把 $i$ 插入其中。那么显然我们的 $i$ 必须是山峰。那么 $f(j-1)$ 就是左边的这一段,需要接上 $i$,也就是说,$f(j-1)$ 的序列中最后一个数必须是山谷,所以说 $j$ 必须为偶数。同样,我们还要乘上右边这一段 $f(i-j)$,上文已经说了,这个方案数完全可以转化为开头是山谷的方案数,所以可以接上 $i$。最后,我们左右两边的数会有交叉组合,所以从 $i-1$ 个数中挑出 $j-1$ 个数放入左边,所以最后要再乘上 $C_{i-1}^{j-1}$。 时间复杂度 $O(n^2)$。 代码(法一): ```cpp #include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; const ll N=4.2e3; ll n,p,ans; ll f[2][N+5]; inline ll read() { ll ret=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();} while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();} return ret*f; } void write(ll x) { static char buf[22];static ll len=-1; if(x>=0) { do{buf[++len]=x%10+48;x/=10;}while(x); } else { putchar('-'); do{buf[++len]=-(x%10)+48;x/=10;}while(x); } while(len>=0) putchar(buf[len--]); } int main() { n=read();p=read(); f[0][1]=0;f[0][2]=1; for(ll i=3;i<=n;i++) { for(ll j=1;j<=i;j++) { f[i&1][j]=(f[(i-1)&1][i-j+1]+f[i&1][j-1])%p; } } for(ll i=1;i<=n;i++) { ans=(ans+f[n&1][i]+f[n&1][i])%p; } write(ans); return 0; } ``` 代码(法二): ```cpp #include<iostream> #include<cstdio> #define ll long long using namespace std; const ll N=4.2e3; ll n,p; ll f[N+5],c[2][N+5]; inline ll read() { ll ret=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();} while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();} return ret*f; } void write(ll x) { static char buf[22];static ll len=-1; if(x>=0) { do{buf[++len]=x%10+48;x/=10;}while(x); } else { putchar('-'); do{buf[++len]=-(x%10)+48;x/=10;}while(x); } while(len>=0) putchar(buf[len--]); } int main() { n=read();p=read(); f[0]=1;c[0][0]=1; for(ll i=1;i<=n;i++) { for(ll j=1;j<=i;j++) { if(j&1) f[i]=(f[i]+(f[j-1]*f[i-j]%p)*c[(i-1)&1][j-1]%p)%p; c[i&1][j]=(c[(i-1)&1][j]+c[(i-1)&1][j-1])%p; } c[i&1][0]=c[(i-1)&1][0]; } write(2ll*f[n]%p); return 0; } ```