地精部落

来源:互联网 发布:栈实现链表反转java 编辑:程序博客网 时间:2024/06/02 07:31
题目描述 Description

    传说很久以前,大地上居住着一种神秘的生物:地精。 
    地精喜欢住在连绵不绝的山脉中。具体地说,一座长度为 N 的山脉 H可分为从左到右的 N 段,每段有一个独一无二的高度 Hi,其中Hi是1到N 之间的正整数。 
    如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。 
    类似地,如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。 
    地精们有一个共同的爱好——饮酒,酒馆可以设立在山谷之中。地精的酒馆不论白天黑夜总是人声鼎沸,地精美酒的香味可以飘到方圆数里的地方。 
    地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,并轮流担当瞭望工作,以确保在第一时间得知外敌的入侵。 
    地精们希望这N 段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足这个条件的整座山脉才可能有地精居住。 
    现在你希望知道,长度为N 的可能有地精居住的山脉有多少种。两座山脉A和B不同当且仅当存在一个 i,使得 Ai≠Bi。由于这个数目可能很大,你只对它除以P的余数感兴趣。

输入描述 Input Description

    输入仅含一行,两个正整数 N,P。 

输出描述 Output Description

输出仅含一行,一个非负整数,表示你所求的答案对P取余之后的结果。

样例输入 Sample Input

4 7

样例输出 Sample Output

3

数据范围及提示 Data Size & Hint

共有10 种可能的山脉,它们是: 
1324 1423 2143 2314 2413 
 
3142 3241 3412 4132 4231   
【数据规模和约定】 
对于 20%的数据,满足 N≤10; 
对于 40%的数据,满足 N≤18; 
对于 70%的数据,满足 N≤550; 
对于 100%的数据,满足 3≤N≤4200,P≤109   

评价:这道题是我看了题解以后才做出来的,真是一道神题,但是写题解的大神都不愿解释得太详细,所以我想了很久才想明白。。。看了题解以后真的觉得很像数的划分、约瑟夫问题还有国王游戏,代码出奇地简洁,但是思维量相当地高。
 题解:
三个引理:
 
①在n->n-1的转化过程中,我们删除了一个点后,我们可以将n-1个点视为仍是1~n-1的排列。
 
②在若排列Pn为一个合法抖动子序列,则交换i∈[1,n)与i+1,必能得到另一个抖动子序列。
 
③抖动序列的对称性,若存在第一段上升的长度为n的抖动子序列,则以n+1-x代x必能得到一个第一段下降的长度为n的抖动子序列。 

设f[i][j]为长度为i的,以j开头的,第一段下降的抖动子序列的个数,则循题意可得2*f[n+1][n+1]即为答案。

考虑转移:

①若j的下一个是j-1,则需要一个长度为n-1的,以j-1开头的上升子序列;

再分两种情况:

若j==n,则j-1为i-1中正数第一个数,所以可以转移到f[i][1];

若j<n,则j-1为i-1中正数第i-1-(j-1)+1-1个数,所以可转移到f[i-1][j-i].

我们根据状态设定,显然有f[i][0]=f[i][1]=0.

∴f[i][j]->f[i-1][j-i].

②若j的下一个不是j-1,则对于任意一个以j-1开头的下降子序列,均可以通过交换j-1和j+1得到。

∴f[i][j]->f[i][j-1]

综,f[i][j]=f[i-1][j-i]+f[i][j-1].

减少代码量?一个技巧:

改变状态,令f[i][j]=f[i+1][j+1],则初始状态变更为f[1][1]=1,i∈[1,n],j∈[1,n].
ps:这个题是2010年山东省赛第一轮第二试的第一题。。竟然如此蛋疼。。。。顿时感觉明年的省赛会很呵呵。。。 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<iostream>  
  2. using namespace std;  
  3. #include<cstdio>  
  4. #include<cmath>  
  5. #include<algorithm>  
  6. #include<cstring>  
  7. int f[2][10001];  
  8. int main(){  
  9.     int n,p;  
  10.     bool tmp;  
  11.     scanf("%d%d",&n,&p);  
  12.     f[1][1]=1;  
  13.     for(int i=2;i<=n;++i){  
  14.         tmp=i&1;  
  15.         for(int j=1;j<=i;++j)  
  16.             f[tmp][j]=(f[!tmp][i-j+1]+f[tmp][j-1])%p;  
  17.     }  
  18.     printf("%d",(f[tmp][n]<<1)%p);  
  19.  
0 0