bzoj1925 [Sdoi2010] 地精部落
题目描述
传说很久以前,大地上居住着一种神秘的生物:地精。
地精喜欢住在连绵不绝的山脉中。具体地说,一座长度为N的山脉H可分为从左到右的N段,每段有一个独一无二的高度Hi,其中Hi是1到N之间的正整数。
如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。
类似地,如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。
地精们有一个共同的爱好——饮酒,酒馆可以设立在山谷之中。地精的酒馆不论白天黑夜总是人声鼎沸,地精美酒的香味可以飘到方圆数里的地方。
地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,并轮流担当瞭望工作,以确保在第一时间得知外敌的入侵。
地精们希望这N段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足这个条件的整座山脉才可能有地精居住。
现在你希望知道,长度为N的可能有地精居住的山脉有多少种。两座山脉A和B不同当且仅当存在一个i,使得Ai≠Bi。由于这个数目可能很大,你只对它除以P的余数感兴趣。
输入输出格式
输入格式:
输入文件goblin.in仅含一行,两个正整数N, P。
输出格式:
输出文件goblin.out仅含一行,一个非负整数,表示你所求的答案对P取余之后的结果。
输入输出样例
说明
【数据规模和约定】
对于20%的数据,满足N≤10;
对于40%的数据,满足N≤18;
对于70%的数据,满足N≤550;
对于100%的数据,满足3≤N≤4200,P≤1e9。
题解
好题……思维难度省选,代码难度普及-……
大佬真厉害
首先先弄清楚三个性质
$$性质一:在一个波动序列中,如果数字i与i+1不相邻,那么交换i与i+1,得到的仍然是一个波动序列$$
$$证明:若i为山峰,交换后i+1必然也为山峰。同理,若i+1为山谷,交换后i也必为山谷$$
$$若i为山谷,因为i+1与i不相邻,所以i两边的高度都大于i+1,所以交换后i+1小于其两边,仍为山谷。同理,若i+1为山峰,交换后i也为山峰$$
$$性质二:把波动序列中的每一个数i换成n+1-i,得到的仍是一个波动序列$$
$$证明:原来是山峰的地方变为山谷,原来是山谷的地方便为山峰,仍是波动序列$$
$$性质三:波动序列具有对称性,即将一个波动序列翻转后仍是波动序列$$
$$证明:正确性显然$$
于是就可以考虑如何写状态转移方程了。设$f[i][j]$表示选了$1$到$i$的数,$j$为第一个数,且$j$为山峰
很明显答案为$\sum_{i=1}^n f[n][i]$
如何转移?
先给出方程:$f[i][j]=f[i][j-1]+f[i-1][i-j+1]$
首先,因为由性质一可知,当$j$与$j-1$不相邻时,$j$作为第一个数和$j-1$作为第一个数的方案数相同
又因为$j-1$为山峰,且是第一个数,故$j$与$j-1$必定不相邻
然后,我们来考虑$j$和$j-1$相邻的情况
选了$j$为山峰,则第二个数$j-1$必然为山谷,后面的数为$[1,j-1]$和$[j+1,i]$
问题转变为求$i-1$个数,$j-1$为开头的山谷的方案数
由性质二可得,$j-1$作为山峰和山谷的方案数相同
又因为山峰和山谷是相对位置关系,所以将$[j+1,i]$区间每一个数减一,对方案无影响
于是我们加上$f[i-1][(i-1+1)-(j-1)]$即可
解决了
ps:代码难度和思维难度真心不成正比……
1 //minamoto 2 #include<bits/stdc++.h> 3 #define N 5005 4 #define inf 0x3f3f3f3f 5 using namespace std; 6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<15,stdin),p1==p2)?EOF:*p1++) 7 char buf[1<<15],*p1=buf,*p2=buf; 8 inline int read(){ 9 #define num ch-'0' 10 char ch;bool flag=0;int res; 11 while(!isdigit(ch=getc())) 12 (ch=='-')&&(flag=true); 13 for(res=num;isdigit(ch=getc());res=res*10+num); 14 (flag)&&(res=-res); 15 #undef num 16 return res; 17 } 18 int n,mod,f[2][N]; 19 int main(){ 20 //freopen("testdata.in","r",stdin); 21 n=read(),mod=read(); 22 f[0][2]=1; 23 for(int i=3;i<=n;++i) 24 for(int j=2;j<=i;++j) 25 f[i&1][j]=(f[i&1][j-1]+f[i-1&1][i-j+1])%mod; 26 int ans=0; 27 for(int j=2;j<=n;++j) 28 ans=(ans+f[n&1][j])%mod; 29 printf("%d\n",(ans<<1)%mod); 30 return 0; 31 }