[sdoi 2010][bzoj 1925]地精部落(dp)

题干:

地精喜欢住在连绵不绝的山脉中。一座长度为N的山脉 H可分 为从左到右的N段,每段有一个独一无二的高度 Hi,其中Hi是1到N之间的正整数。 如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边 缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。酒馆可以设立在山谷之中。地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,地精们希望这N段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足这个条件的整座山脉才可能有地精居住。 现在你希望知道,长度为N 的可能有地精居住的山脉有多少种。两座山脉A 和B不同当且仅当存在一个i,使得 Ai≠Bi。由于这个数目可能很大,你只对它除以P的余数感兴趣。

对于 20%的数据,满足 N≤10;
对于 40%的数据,满足 N≤18;
对于 70%的数据,满足 N≤550;
对于 100%的数据,满足 3≤N≤4200,P≤10^9

题解:

首先,我们的目标序列一定是山峰与山谷交替出现的,也就是说,当x位置是山峰,我们就要从剩下的且比x处山峰高度小的数中挑一个,放到x+1位置上,反之要挑大的数。根据题干中的“连绵不绝的山脉”,我们可以知道1~n中的所有数在n个山峰中都出现且仅出现一次。。。(一本正经地说这么显然的东西。。。)既然是“连绵不绝”,我们手模几个小样例就可以发现

0%输出样例

40%状压
  先看一下数据范围,发现40%的点其实是可以作为状压dp来写,但dp表示什么呢?一般来说,状压dp[i][j]表示枚举到第i位时状态为j时的方案数(本题所求的是方案数)。而到本题适用吗?想一下:枚举到第i位时我安置的房子状态为j。。。好像是可以,但状态怎么表示?010101或101010?不可取。反正本蒟蒻作者没有想到,咕咕咕。。。(40%的算法都想不到。。。)其实dp[i][j][0/1]状态无非就是:j中0表示未选这个数,1表示已选这个数;第三维中0表示山谷,1表示山峰。那么dp[i][j][1]+=dp[i-1][j^k][0];(k表示未选过的并比现在选择的数小的数);dp[i][j][0]+=dp[i-1][j^k][1];(k表示未选过的并比现在选择的数大的数)。

70% O(n3)

  5503勉强能承受。我们记录状态为dp[i][j][0/1],i表示剩下i个数,j表示有j-1个数小于刚刚选择的数,当下标为0时,表示山谷,当下标为1时,表示山峰(其实正反都可以)。根据我们所定义的转移方程,dp[i][j][0]可以影响dp[i-1][j~i][1],dp[i][j][1]可以影响dp[i-1][1~j-1][0]。那么我们就可以初始化dp[n][0][0]=dp[n][n+1][1]=1(刚开始所有的数都可以作为第一个数为山峰或山谷,要么没有数比上一层大,要么所有数都比上一层大),最后求dp[0][1][0]+dp[0][1][1]即可。

100% O(n2)

  其实在n3的算法中,我么没事在不断刷表,一个一个加。我们可以将刷表改为填表,比较容易地就可以发现dp[i][j][1]=Σjx=0 dp[i+1][x][0];dp[i][j][0]=Σi+2x=j+2 dp[i+1][x][1](其中的Σ可以在枚举的过程中顺便求得),加上滚动数组,完美

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 const int maxn=4200+10;
 5 int dp[2][maxn][2],n,mod,cur;
 6 int main(){
 7     scanf("%d%d",&n,&mod);
 8     memset(dp,0,sizeof(dp));
 9     dp[cur][0][0]=dp[cur][n+1][1]=1;
10     for(register int i=n-1;i>=0;--i){
11         cur^=1;
12         for(register int j=0;j<=i+2;++j) dp[cur][j][0]=dp[cur][j][1]=0;
13         int x=dp[cur^1][0][0];
14         for(register int j=1;j<=i+1;++j){
15             x=(x+dp[cur^1][j][0])%mod;
16             dp[cur][j][1]=(dp[cur][j][1]+x)%mod;
17         }
18         x=dp[cur^1][i+2][1];
19         for(register int j=i+1;j>=1;--j){
20             dp[cur][j][0]=(dp[cur][j][0]+x)%mod;
21             x=(x+dp[cur^1][j][1])%mod;
22         }
23     }
24     printf("%d",(dp[cur][1][0]+dp[cur][1][1])%mod);
25 }
Code

100% O(n2) plus

  我们还可以设dp[i][j]为已经有i个数,且首项为j且j为波峰的方案总数。既然确定了首项,我们在来考虑第二项,若第二项不为j-1,那么我们将这两个数换位发现仍然为抖动数列(j为首项且为波峰),我们把所有这种情况下的序列都交换j与j-1的位置,那么这种情况方案数为dp[i][j-1]。我们在来考虑第二项为j-1的情况,这种情况就比较麻烦,我们去掉第一个数j后,所剩下的就只有[1~j-1]和[j+1~i],其实这i-1个数就便成了我们dp[i-1]的情况了(将[j+1~i]减1),那么就得到了一个[1~i-1]的抖动序列,再使j-1为波谷,此种情况方案总数为f[i-1][i-1-(j-1)+1]=f[i-1][i-j+1](我们手模几个点就可以发现,当我们将所有的数都由i变为n-i+1时同样成立-->波峰变波谷。因为对称性我们把ans×2,就得到了结果(不是在转移时就已经考虑了对称时的乘2了吗?为什么还要乘2呢?其实我们上文的转移是将那位的数变化了,是内部的变化,只是相当于波峰变波谷;而最后的乘2是对全局考虑,真正地将波峰变为波谷)。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 4444
 4 using namespace std;
 5 int m,n,mod,dp[2][$],ans;
 6 signed main(){
 7     scanf("%d%d",&n,&mod);
 8     if(n==1){    printf("%d\n",1%mod); return 0;    }
 9     dp[0][1]=1;
10     for(register int i=2,now=1,las=0;i<=n;++i,las=now,now^=1){
11         for(register int j=1;j<=i;++j)
12             dp[now][j]=(dp[now][j-1]+dp[las][i-j+1])%mod;
13     }
14     for(register int i=1;i<=n;++i) ans=(ans+dp[(n-1)%2][i])%mod;
15     printf("%d\n",(ans<<1)%mod);
16 }
code

 

posted @ 2019-07-04 12:19  OI_zzyy  阅读(136)  评论(0编辑  收藏  举报