[SDOI2010]地精部落

link

一道思考起来很有难度但写出来极其简单的题目。看完题解之后顿时觉得我是傻逼。

本题具有早期省选的特征,即混乱的题目描述。但还是可以很容易概括出题面,即构造一些全排列使其中没有连增或连减,问方案总数。

要做这道题就必须搞清楚这个排列具有的一些性质。

  • 全排列这个条件约等于废话,实际上方案数和这些数是什么并没有什么关系,只要元素之间两两不同即可。

显然可以得到。因为题目的限制条件是元素之间的大小关系,所以 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;
}
posted @ 2022-05-14 17:39  Feyn618  阅读(22)  评论(0编辑  收藏  举报