2019CCPC哈尔滨 i题 Interesting Permutation 计数DP做法

题意:定义fi为前i个数的最大值,gi为前i个数的最小值,hi = fi - gi

现在告诉你每个位置的hi,问有多少种1~n的排列满足这些hi。

首先理性分析一波,第i位置的点必须要小于n,并且大于等于i-1,还要大于等于上一个数字。

赛时的我分了三种情况讨论,当前已经选了上边界的点,即n点,当前已经选了下边界的点,即1,还有当前并没有选择边界点。并将选了前i个数字的三种情况的方案数分别记为dp[i][0],dp[i][1],dp[i][2]。。赛后发现好像dp[i][0]永远等于dp[i][1]啊。。两种就可以了。

那么只需要讨论当前位置的h[i]和上一个h[i]是相等还是大于就可以判断出转移的方程式了。

h[i] == h[i-1]:假如当前已选数字中最大值和最小值的差值为h[i],且之前选了i个数字,那么接下来能选的数字就有(h[i] - i + 2)种情况,所以直接dp[i][j] *= (h[i] - i + 2)即可

h[i] > h[i-1]:先考虑未选择边界点时的情况。假设已选的序列的最小值为a,由于最小值和最大值都不为边界点,则可以得到

a > 1 && a + h[i-1] < n

能够算出有多少种a是符合的条件的。假设有Q种a是符合上述式子的,则每一种a所对应的的方案数是相同的。(注释1)所以可以得到每一种最小值的方案数,即dp[i-1][2] / Q, 再算出当最大值最小值之差变为h[i]时又多少种a是符合的(设为k),则可以推出dp[i][2] = dp[i-1][2] / Q * k,其中除法要预处理逆元。

注释1的证明:假设对于两个符合上述式子的最小值a,b。以对于任意一个以a为最小值的序列,每个元素都加上(b-a),则可以得到一个以b为最小值的序列。因为a,b都是满足上式的,加了(b-a)也不会使得序列不合法

然后再去考虑此时从dp[i-1][2]转移到dp[i][0] dp[i][1]的情况,发现只会有一种合法的a转移到边界。即a = n - h[i],当然,我们无需知道a的具体值,只需要知道有一种就行了。所以dp[i][0] += dp[i-1][2] / Q + dp[i-1][0].      dp[i][1]同理

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IO ios::sync_with_stdio(false)
#define rep(i,a,n) for (int i = a; i <= n; i++)
ll pow_mod(ll a,ll b,ll c=mod,ll ans=1){while(b){if(b&1) ans=(a*ans)%c;a=(a*a)%c,b>>=1;}return ans;}
ll inv_mod(ll a){return pow_mod(a,mod-2);}
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
ll dp[maxn][3], inv[maxn], co[maxn];
int main(){
	IO;
	int n, m, t;
	rep(i,0,1e5)
		inv[i] = inv_mod(i);
	cin >> t;
	while(t--){
		cin >> n;
		rep(i,1,n){
			cin >> co[i];
			rep(j,0,2)
				dp[i][j] = 1;
		}
		if(co[i] != 0){
			cout << 0 << endl;
			continue;
		}
		dp[1][0] = dp[1][1] = 1; dp[1][2] = n - 2;
		bool flag = 1;
		for(int i = 2; i <= n && flag; i++){
			if(co[i] < i - 1 || co[i] >= n || co[i] < co[i-1])
				flag = 0;
			else if(co[i] == co[i-1]){
				rep(j, 0, 2)
					dp[i][j] = (dp[i-1][j] * (co[i] - i + 2)) % mod;
			}else{
				if(co[i] != n - 1){
					int minn = max(0, n-2-co[i-1]);
					if(co[i] == n-1)
						minn = 0;
					dp[i][0] = (dp[i-1][0] + (dp[i-1][2] * inv[minn])) % mod;
					dp[i][1] = (dp[i-1][1] + (dp[i-1][2] * inv[minn])) % mod;
					dp[i][2] = (dp[i-1][2] * inv[minn] % mod * (max(0, n-2-co[i]) % mod * 2)) % mod;
				}
			}
		}
		if(!flag)
			cout << 0 << endl;
		else
			cout << (dp[n][0] + dp[n][1]) % mod << endl;
	}
	
}

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值