[SDOI2010]地精部落 DP
LG传送门
DP好题
题意很简单,就是求1~n的排列,满足一个数两边的数要么都比它大要么都比它小,求这样的排列个数对\(p\)取膜的值(为了表述简单,我们称这样的排列为波动序列)。
这个题我第一眼看到时自然是懵逼的,然后果断看题解,题解里有五种我觉得还不错的方法,但是有些讲的不太清楚,所以我就自己写一篇。
第一种
先证两条引理(自己手玩一下就可以证明了)
引理1:在一个波动序列中,如果\(i-1\)与\(i\)不相邻,交换\(i-1\)与\(i\)即可得到一个新的波动序列。
引理2:把长度为\(n\)一个波动序列中的数字\(a_i\)变成\((n+1)-a_i\)会得到一个新的波动序列,且新波动序列的山峰和山谷与原序列相反。
状态与转移
设\(f[i][j]\)表示由1~i这些数组成的,满足第一个数为\(j\),且\(j\)为山峰的波动序列数。
考虑转移,我先写上转移方程:
\(\qquad f[i][j]=f[i][j-1]+f[i-1][i-j+1]\)
请结合下面的文字描述理解。
由引理1知若\(j-1\)与\(j\)不相邻,交换\(j-1\)与\(j\)之后会得到一些波动序列,这些序列就是以\(j-1\)开头的所有波动序列,且这些序列与交换前的序列一一对应,所以应该加上一个\(f[i][j-1]\)表示以\(j\)开头的一些波动序列可以由以\(j-1\)开头的波动序列转移过来。
若\(j-1\)与\(j\)相邻,既然我们规定了\(j\)是山峰,那么\(j-1\)就一定是山谷,仿照上面的推导过程,由引理2中的一一对应关系可知,长度为\(i-1\)、以\(j-1\)开头且\(j-1\)为山谷的序列数与以\((i-1+1)-(j-1)\)即\(i-j+1\)开头且\(i-j+1\)为山峰的序列数相同,所以需要加上一个\(f[i-1][i-j+1]\)。
最后的答案就是\(2*\sum_{i=2} ^n f[n][i]\)(反正以\(1\)开头构不成波动序列即\(f[n][1]=0\),所以不用从\(1\)开始加),×2是因为上面只考虑了开头是山峰的情况,由引理2知对于每一种上面算过的情况一定有且仅有一种开头是山谷的情况与之对应。
我觉得我的证明已经很不西江月了。
代码实现
可以用滚动数组优化空间。
#include<cstdio>
using namespace std;
const int S=4211;
int f[2][S];
int main(){
register int n,p,i,j,o=0;
scanf("%d%d",&n,&p),f[0][2]=1;
for(i=3;i<=n;++i)
for(j=2;j<=i;++j)
f[i&1][j]=(f[i&1][j-1]+f[(i-1)&1][i-j+1])%p;
for(i=2;i<=n;++i) o=(o+f[n&1][i])%p;
printf("%d",(o<<1)%p);
return 0;
}
实测221ms,792kb。
第二种
一种比较朴实沉毅的方法。
首先有一个显然的结论是波动序列一定是山峰山谷交替出现的,再利用上一种解法的引理2,可知对于1~n的数山峰先出现的序列数一定与山谷先出现的序列数相等。还有一个显然的结论,只要数字的个数相同,组成波动序列的方案数就一定相同(我在这里说是显然的结论一定真的是显然的,如果你觉得不显然一定是你没有认真想) 。
状态与转移
设\(f[i]\)表示1~i共有多少种先降后升的波动序列,最后答案就是\(f[n]*2\)。转移时枚举\(i\)插在第\(j\)个位置且必须在山峰(由于是先降后升的序列,所以\(j\)一定是奇数),在\(i-1\)个数中取\(j-1\)个(\(C_{i-1}^{j-1}\))放在左边(\(f[j-1]\),注意前面提到的第二条显然结论),剩下的\(i-j\)个放在右边(\(f[i-j]\),把先降后升的序列变成先升后降的序列才能与前面相接,但方案数是一样的),于是有转移方程:
\(\qquad f[i]= \sum _ {j = 1} ^iC_{i-1}^{j-1}*f[j-1]*f[i-j]\)。
代码实现
同样用了滚动数组。
#include<cstdio>
using namespace std;
const int S=4211;
int c[2][S],f[S];
int main(){
register int n,p,i,j;
scanf("%d%d",&n,&p),c[0][0]=c[1][0]=f[0]=1;
for(i=1;i<=n;++i)
for(j=1;j<=i;++j){
if(j&1) (f[i]+=1ll*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;
}
printf("%d",2ll*f[n]%p);
return 0;
}
实测397ms,842kb。
第三种
状态与转移
定义一个块表示只能在两头加入数字的一段,设\(f[i][j][k]\)表示1~i的数字被分成\(j\)块,两端上有\(k(k\leq2)\)个位置不能插入数字(即把两端的块与边界连接起来)。有三种转移:
把已有的两个块合在一起:\(f[i+1][j-1][k]+=f[i][j][k]*(j-1)\);
把两端中的某一端与边界连接起来:\(f[i+1][j][k-1]+=f[i][j][k]*(2-k)\);
新建一个块:\(f[i+1][j+1][k]+=f[i][j][k]*(j+1-k)\)。
如果你怀疑为什么没有一种转移是在某个块的边上插入数字\(i\)而不把这个块与另一个块或边界连起来,注意在\(i\)之前插入的数都比\(i\)小,而在\(i\)之后插入的数都比\(i\)大,如果不把它与另一个块或边界连起来,那么在它的一边连的是比它小的数而另一边连的是比它大的数,就不符合条件了。
最后的答案就是\(f[n][1][0]+f[n][1][1]+f[n][1][2]\)。
代码实现
仍然用了滚动数组。
#include<cstdio>
#include<cstring>
using namespace std;
const int S=4211;
long long f[2][S][3];
inline int min(int x,int y){return x<y?x:y;}
int main(){
register int n,p,i,j,k,a,m;
scanf("%d%d",&n,&p),a=1,f[1][1][0]=1;
for(i=1;i<n;++i,a^=1){
memset(f[a^1],0,sizeof f[a^1]),m=min(i,n-i+1);
for(j=1;j<=m;++j)
for(k=0;k<=2;++k)
if(f[a][j][k]){
if(j>1) (f[a^1][j-1][k]+=f[a][j][k]*(j-1)%p)%=p;
if(k<2) (f[a^1][j][k+1]+=f[a][j][k]*(2-k)%p)%=p;
(f[a^1][j+1][k]+=f[a][j][k]*(j+1-k)%p)%=p;
}
}
printf("%lld",(f[a][1][0]+f[a][1][1]+f[a][1][2])%p);
return 0;
}
实测1065ms,1036kb。
第四种
这个方法最好自己一边手动模拟一边感性理解。
状态与转移
设\(f[i][j]\)表示1~i的数字构成的序列,有\(j\)个不合法的方案数。考虑到新加入的数字比前面的都大,因此它只会对它两边的数中更大的那一个造成影响,根据它造成的影响,有三种转移:
合法\(\Rightarrow\)合法:如果位于序列两头的数是山峰,就把新加入的数放在它与另一个数之间,如果是山谷,就把新加入的数放在端点上,\(f[i][j]+=f[i-1][j]*2\);
不合法\(\Rightarrow\)合法:放在一个不合法数与在他旁边比它更小的数之间,\(f[i][j]+=f[i-1][j+1]*(j+1)\);
合法\(\Rightarrow\)不合法:剩下的位置,\(f[i][j]+=f[i-1][j-1]*(i-j-1)\);
没有 不合法\(\Rightarrow\)不合法 的情况。
代码实现
一开始没用滚动数组被卡了空间。
#include<cstdio>
#include<cstring>
using namespace std;
const int S=4211;
long long f[2][S];
int main(){
register int n,m,p,i,j,a=1;
scanf("%d%d",&n,&p),f[a][0]=4,f[a][1]=2;
for(i=4;i<=n;++i){
a^=1,memset(f[a],0,sizeof f[a]);
for(j=0,m=i-1;j<m;++j){
(f[a][j]+=f[!a][j]<<1)%=p;
(f[a][j]+=f[!a][j+1]*(j+1))%=p;
if(j) (f[a][j]+=f[!a][j-1]*(i-j-1))%=p;
}
}
printf("%lld",f[a][0]);
return 0;
}
实测849ms,812kb。
第五种
其实与第四种比较类似,但是洛谷上的题解有点问题,所以在这里说一下。
状态与转移
设\(f[i][j]\)表示1~i的数字构成的序列,有\(j\)个不合法且这个数的后一个数比它大的方案数。考虑到新加入的数字比前面的都大,因此它只会对它两边的数中更大的那一个造成影响,根据它造成的影响,有三种转移:
合法\(\Rightarrow\)合法:如果位于序列尾的数是山峰,就把新加入的数放在它之前,如果是山谷,就把新加入的数放在它之后,\(f[i+1][j]+=f[i][j]\);
不合法\(\Rightarrow\)合法:放在一个不合法数之前,\(f[i+1][j-1]+=f[i][j]*j\);
合法\(\Rightarrow\)不合法:剩下的位置,\(f[i+1][j+1]+=f[i][j]*(i-j)\);
没有 不合法\(\Rightarrow\)不合法 的情况。
注意这里的状态与上一种解法的不同点,答案是\(f[n][0]*2\)。
代码实现
相信你不敢不用滚动数组 然而一开始没用滚动数组似乎能过。
#include<cstdio>
#include<cstring>
using namespace std;
const int S=4211;
long long f[2][S];
int main(){
register int n,p,i,j,a=1;
scanf("%d%d",&n,&p),f[a][0]=1;
for(i=1;i<=n;++i,a^=1){
memset(f[!a],0,sizeof f[!a]);
for(j=0;j<=i;++j){
(f[!a][j]+=f[a][j])%=p;
if(j) (f[!a][j-1]+=f[a][j]*j)%=p;
(f[!a][j+1]+=f[a][j]*(i-j))%=p;
}
}
printf("%lld",(f[!a][0]<<1)%p);
return 0;
}
实测1139ms,804kb。
对这五种解法的总结
等我多做点题再说吧。
[SDOI2010]地精部落 DP的更多相关文章
- BZOJ 1925: [Sdoi2010]地精部落( dp )
dp(i,j)表示1~i的排列中, 以1~j为开头且开头是下降的合法方案数 这种数列具有对称性, 即对于一个满足题意且开头是上升的n的排列{an}, 令bn = n-an+1, 那么{bn}就是一个满 ...
- [BZOJ1925][SDOI2010]地精部落(DP)
题意 传说很久以前,大地上居住着一种神秘的生物:地精. 地精喜欢住在连绵不绝的山脉中.具体地说,一座长度为 N 的山脉 H可分 为从左到右的 N 段,每段有一个独一无二的高度 Hi,其中Hi是1到N ...
- 【BZOJ】1925: [Sdoi2010]地精部落 DP+滚动数组
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1925 题意:输入一个数N(1 <= N <= 4200),问将这些数排列成折线 ...
- Luogu2467 SDOI2010 地精部落 DP
传送门 一个与相对大小关系相关的$DP$ 设$f_{i,j,0/1}$表示放了$i$个,其中最后一个数字在$i$个中是第$j$大,且最后一个是极大值($1$)或极小值时($0$)的方案数.转移: $$ ...
- P2467 [SDOI2010]地精部落 DP
传送门:https://www.luogu.org/problemnew/show/P2467 参考与学习:https://www.luogu.org/blog/user55639/solution- ...
- Luogu 2467[SDOI2010]地精部落 - DP
Solution 这题真秒啊,我眼瞎没有看到这是个排列 很显然, 有一条性质: 第一个是山峰 和 第一个是山谷的情况是一一对应的, 只需要把每个数 $x$ 变成 $n-x+1$ 然后窝萌定义数组 $ ...
- 【BZOJ1925】[Sdoi2010]地精部落 组合数+DP
[BZOJ1925][Sdoi2010]地精部落 Description 传说很久以前,大地上居住着一种神秘的生物:地精. 地精喜欢住在连绵不绝的山脉中.具体地说,一座长度为 N 的山脉 H可分 为从 ...
- 【BZOJ1925】[SDOI2010]地精部落(动态规划)
[BZOJ1925][SDOI2010]地精部落(动态规划) 题面 BZOJ 洛谷 题解 一道性质\(dp\)题.(所以当然是照搬学长PPT了啊 先来罗列性质,我们称题目所求的序列为抖动序列: 一个抖 ...
- [BZ1925] [SDOI2010]地精部落
[BZ1925] [SDOI2010]地精部落 传送门 一道很有意思的DP题. 我们发现因为很难考虑每个排列中的数是否使用过,所以我们想到只维护相对关系. 当我们考虑新的一个位置时,给新的位置的数分配 ...
随机推荐
- win10中显示资源管理器扩展
一年前从有两台机器win7升级到win10,一台上装了我常用的资源管理器扩展setExBar,但另一台没有.升级后原来有插件的依然默认显示插件,我在另一台机器上安装了setExBar时默认不显示.如果 ...
- 利用memcached实现分布式锁
一 需求场景: (1) 需要限制用户创建提现订单的频率:目的一是防止前端bug引起的用户重复提交:二是防止并发攻击绕过提现策略(第一次提现和第二次提现门槛可能不同). (2) 需要限制秒杀下同一用户 ...
- C/C++——指针,引用做函数形参
函数中的形参是普通形参的时,函数只是操纵的实参的副本,而无法去修改实参. 引用形参是对实参的直接操纵,指针形参是对 它所指向的值(*p) 的直接操纵,但是对于这个指针变量(p)来说,依然只是副本. 指 ...
- 非接触式读卡器13.56MHZ芯片:SI522
对于现在的智能锁市场需求竞争极大,中小型厂家月销量更是在慢慢增长.刷卡功能更是智能锁的标配功能,当然13.56Mhz芯片现在讲究的就是超低功耗,为满足市场需求专注于物联网多年的中科微强力推出了13.5 ...
- CSU - 1581 Clock Pictures (KMP的变形题,难想到)
题目链接: http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1581 题目意思:告诉你现在有两个钟,现在两个钟上面都有n个指针,告诉你指针的位置, ...
- Maven创建Web项目、、、整合SSM框架
自己接触ssm框架有一段时间了,从最早的接触新版ITOO项目的(SSM/H+Dobbu zk),再到自己近期来学习到的<淘淘商城>一个ssm框架的电商项目.用过,但是还真的没有自己搭建过, ...
- CORS support for ASP.NET Web API (转载)
CORS support for ASP.NET Web API Overview Cross-origin resource sharing (CORS) is a standard that al ...
- cocos2dx lua 一键资源管理PowerShell脚本实现
特别说明 此管理脚本不包含图片资源加密,热更新资源文件列表是md5 和 文件路径构成的txt,如下 脚本文件是放在和res src 同级的文件夹里面 脚本内容如下 clear $PSDefaultPa ...
- iOS:cocoapods 配置相关(19-04-02更)
1.gem sources 2.libwebp 1.gem sources 因为,mac更新,cocoapods也要更新,使用下面指令,提示找不到.org,原因是淘宝的镜像源.org换成.com,所以 ...
- react 配置开发环境
一:先自行下载安装node和npm 二:cnpm install create-react-app -g 三:create-react-app my-project 四:cd my-project ...