[Bzoj1925]&[SDOI2010]地精部落 DP

来源:互联网 发布:wpf 管理系统源码下载 编辑:程序博客网 时间:2024/04/28 20:41

题目链接:Bzoj1925 .

—————————————-

概述

题目大意如下。

给定一个数n,问1 ~ n的所有排列中有多少个排列满足要求:对于任意的i,都有Ai1<Ai>Ai+1或者Ai1>Ai<Ai+1(对于第一个数和最后一个数没有要求).答案对p取模。

其中,1n4200, p109.

—————————————-

分析

作为一个DP蒟蒻,这题折磨了我很久。

首先明确一点,这是一个计数问题,应该可以DP(钦定算法2333)。那么接着的问题就是状态的选取了,这题的状态选取范围太广泛,不同的状态选取复杂度都不一样,我接下来讲一讲我在网上学习的做法,代码很短很巧妙。

定义dp[i][j]1 ~ i的排列,j个数是不合要求的方案数,其中一段连续的不合要求的数必须递增。我下面将举例说明。

对于5 1 2 3 4这个序列,34是不合要求的。因为不看3后面的数的话,3本应该比2小;不看4后面的数,4本应该比3小。所以这里不合法的数有2个。

对于5 1 2 4 3这个序列,只有4是不合要求的。因为不看4后面的数,4本应该比2小。所以这里不合法的数有1个。

对于4 5 3 2 1这个序列,我们不作考虑。因为它虽然有3 2 1这个不合法的序列,但是我们可以把它等效成1 2 3这个子序列,所以我们只需要考虑4 5 1 2 3这个序列,最后答案乘2就能把4 5 3 2 1的方案数统计进去了。

状态的含义讲完了,接下来是转移。假设当前序列不合法的数有j个,那么插入一个数得到的新序列的不合法数只可能是j1个、j个或j+1个。

那么我们考虑不合法数的改变情况,可以得到如下转移:

  1.  dp[i+1][j]=dp[i+1][j]+dp[i][j];

    我们将i+1插入到当前序列的末尾或者最后一个数前面,这两个位置中一定有且仅有一个不会改变新序列不合法数的个数,而且能使新序列不合法数的个数不变的位置只可能是这两个之一。

  2.  dp[i+1][j1]=dp[i+1][j1]+dp[i][j]j;

    我们将i+1插入到当前序列不合法数的前面,可以使得新序列不合法数-1.

  3.  dp[i+1][j+1]=dp[i+1][j+1]+dp[i][j](ij);

    我们将i+1插入到当前序列剩下的位置,可以使新序列不合法数+1.

转移就是这样,最后答案乘2即可。

—————————————-

代码

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>#include<cmath>#define ll long long#define For(i, j, k) for(int i = j; i <= (int)k; ++ i)#define Forr(i, j, k) for(int i = j; i >= (int)k; -- i)#define INF 0x3f3f3f3fusing namespace std;const int maxn = 4200 + 5;int n;ll mo;ll dp[maxn][maxn];int main(){    scanf("%d%lld", &n, &mo);    dp[1][0] = 1;    For(i, 1, n)        For(j, 0, i){            (dp[i+1][j] += dp[i][j]) %= mo;            (dp[i+1][j-1] += dp[i][j]*j) %= mo;            (dp[i+1][j+1] += dp[i][j]*(i-j)%mo) %= mo;        }    printf("%lld", (dp[n][0]<<1) % mo);    return 0;}

—————————————-

小结

这题的方法其实还有很多,但是这一种比较简洁所以选择学一学,可能不太好理解吧。设好状态之后,解题的关键就是分位置讨论新的序列的不合法数,是道思维好题。

—————————————-

wrote by miraclejzd.

原创粉丝点击