[SDOI2010]地精部落
一道思考起来很有难度但写出来极其简单的题目。看完题解之后顿时觉得我是傻逼。
本题具有早期省选的特征,即混乱的题目描述。但还是可以很容易概括出题面,即构造一些全排列使其中没有连增或连减,问方案总数。
要做这道题就必须搞清楚这个排列具有的一些性质。
-
全排列这个条件约等于废话,实际上方案数和这些数是什么并没有什么关系,只要元素之间两两不同即可。
显然可以得到。因为题目的限制条件是元素之间的大小关系,所以 1 2 3
的方案数并不会和1 2 5201314
有什么区别。
-
方案数会是偶数。
换句话说,所有方案实际上是两两对应的。显然对于方案 \(A\) ,可以构造另一个满足题意的方案 \(B,\forall i\in[1,N],B_i=N+1-A_i\)。而很显然两个方案的峰谷关系是对应相反的。
-
对于排名相邻的两个数 \(a,b\) ,如果它们在一个合法方案里不相邻,那么交换它们的位置得到的新方案也会是合法的。
这就比较难想到了。
还是一样的。由于题目中的限制条件都是针对大小关系而言的,所以对于其它数来说,它们其实可以看成是一个数,交换之后改变数列的大小关系当且仅当两个数位置相邻。
所以就可以列方程了:
假设 f[i][j]
代表i个互不相同的数排合法序列,第一个数是排名第j的数且作为山峰的方案数。那么对i和i-1的位置分类讨论。如果序列第二个数不是i-1,那么会有f[i][j]+=f[i][j-1]
。因为交换两个数并不会改变方案的合法性。但如果两个数相邻呢?这个序列的前两个数相当于就固定了,我们会得到f[i][j]+=g[i-1][j-1]
,表示用i-1个数排成一个排名j-1为队首且队首为谷的方案数。而由于第二个性质,有 g[i][j]=f[i][i+1-j]
,那么方程就可以整理为 f[i][j]=f[i][j-1]+f[i-1][i-j+1]
,枚举更新即可。可以用滚动数组优化但我懒得写。
代码极其简洁,但思维难度真的挺高的。
#include<cstdio>
#define zczc
const int N=4300;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
int m,mod,ans,f[N][N];
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(mod);
f[1][1]=1;
for(int i=2;i<=m;i++)
for(int j=2;j<=i;j++)
f[i][j]=(f[i][j-1]+f[i-1][i-j+1])%mod;
for(int i=1;i<=m;i++)ans=(ans+f[m][i])%mod;
printf("%d",(ans*2)%mod);
return 0;
}
一如既往,万事胜意