题解:错排问题

题面:给定 \(1\le n\le 1000\)\(1\le m\le 2000\),对每个 \(1\le k\le m\) 求有多少个长为 \(n\) 的错排满足 \(\sum\limits_{i=1}^n|i-p_i|=k\)。1s,256MB。

题解

将结构视作二分图形式,左部点 \(i\) 向右部点 \(p_i\) 连边,那 \(\sum\limits_{i=1}^n|i-p_i|\) 就转化为每条边的长度之和,而这个东西可以转化为穿过每条缝隙的边之和,如图所示。

考虑设 \(dp_{i,j,k}\) 表示目前考虑到 \(i\),有 \(j\) 对点仍未确定它所在的边的另一个点,目前的总距离和是 \(k\)。转移如下:

  • \(dp_{i,j,k}\to dp_{i+1,j+1,k+j+1}\):位置 \(i\) 与数字 \(i\) 都连向未来的一个未知点。

  • \(dp_{i,j,k}\to dp_{i+1,j,k+j}\times 2j\):位置 \(i\) 跟之前的一个点配对,数字 \(i\) 连向未来的一个未知点,反过来同理,此时方案数为 \(2j\)

  • \(dp_{i,j,k}\to dp_{i+1,j-1,k+j-1}\times j^2\):位置 \(i\) 与数字 \(i\) 都与之前的一个点配对,方案数为 \(j^2\)

直接 dp 复杂度 \(\mathcal O(n^2m)\)

注意到因为每次转移,第三维都会加上第二维的值,所以它显然满足 \(j(j-1)\le m\)。故 \(j\) 的上界为 \(\sqrt m\),复杂度为 \(\mathcal O(nm^{1.5})\)

代码

需要滚动数组。实现使用了 pull 而不是 push。

int n,m,dp[2][50][2005];
void solve(){
	cin>>n>>m,dp[0][0][0]=1;
	fo(i,1,n){
		int p=i&1,q=p^1;
		mem(dp[p],0);
		fo(j,0,46)fo(k,j,m){
			if(j)cplus(dp[p][j][k],dp[q][j-1][k-j]);
			cplus(dp[p][j][k],(int)(2ll*j*dp[q][j][k-j]%mod));
			cplus(dp[p][j][k],(int)(1ll*(j+1)*(j+1)*dp[q][j+1][k-j]%mod));
		}
	}
	fo(i,0,m)cout<<(i&1?0:dp[n&1][0][i/2])<<' ';
}
posted @ 2026-08-21 16:29  qwertim  阅读(6)  评论(0)    收藏  举报