P8386 [PA2021] Od deski do deski

发布时间 2024-01-07 19:35:32作者: SunsetLake

一道很抽象的 \(\text {dp}\)?

状态就比较抽象。注意到 \(m\)\(10^9\),肯定不能带到状态里。但是我们可以注意到:如果当前序列 \(S\) 已经合法,且有 \(S+x\) 合法,那么 \(S+x+x\) 也一定合法,因为我们可以把两个 \(x\) 消掉。因此,可以设计 \(f_{i,j}\) 表示长度为 \(i\) 的序列,往后放 \(j\) 种数依然合法的方案数。同时还要考虑当前序列是否合法,于是增加一维 \(0/1\) 表示当前序列是否合法。

然后考虑转移。首先如果当前为 \(f_{i,j,1}\),那么长为 \(i+1\) 的序列再往后放这 \(j\) 种数依然合法,因为可以互相消掉,且种类不会增多。当前这一位可以有 \(j\) 种放法,所以 \(f_{i+1,j,1} \gets f_{i,j,1} \times j\)。同时如果当前 \(f_{i,j,0}\) 是不合法的,那就是说单出来一个数不能配对,因此我们考虑在 \(i+1\)\(j\) 种数中的一个,使它能与之前的配对并消除,变成合法.因此 \(f_{i+1,j,1} \gets f_{i,j,0} \times j\)

再来看不合法的情况。如果当前 \(f_{i,j,1}\),我们在后面填了另外的 \(m-j\) 种数,此时这个数就会单出来无法与前面的匹配消掉,并会让种数加一,\(f_{i+1,j+1,0} \gets f_{i,j,1} \times (m-j)\)。同理,如果当前不行,那再加那 \(m-j\) 种数中的一个依然不合法。所以 \(f_{i+1,j,0} \gets f_{i,j,0} \times (m-j)\)

答案就是 \(\sum_{i=1}^{n} f_{n,i,1}\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll m,dp[3005][3005][2],ans;
const int mod=1e9+7;
int main(){
	cin>>n>>m;
	dp[0][0][1]=1;
	for(int i=0;i<n;++i){
		for(ll j=0;j<=i;++j){
			dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][1]*j%mod)%mod;
			dp[i+1][j+1][0]=(dp[i+1][j+1][0]+dp[i][j][1]*(m-j)%mod)%mod;
			dp[i+1][j][0]=(dp[i+1][j][0]+dp[i][j][0]*(m-j)%mod)%mod;
			dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][0]*j%mod)%mod;
		}
	}
	for(int i=1;i<=n;++i)ans=(ans+dp[n][i][1])%mod;
	cout<<ans;
	return 0;
}