APIO2016 划艇 计数DP

APIO2016 划艇 计数DP

题意

给出序列\(a,b\),要求有多少个序列满足\(c_i = 0\)或者\(a_i \le c_i \leq b_i\) \((0 < a_i \leq b_i)\) 其中要求非\(0\)部分严格递增

\[1 \leq N \leq500\1 \leq a_i \leq b_i \leq10^9 \]

分析

考虑直接\(dp\)

\(dp[i][j]\)表示前\(i\)个数中,第\(i\)个数非0,且选择\(j\)的方案数

答案就是\(\sum_i \sum_j dp[i][j]\)

转移

\[dp[0][1] = 1\dp[i][j] = \sum_{p = 0}^{i - 1}\sum_{k = 0}^{j - 1}dp[p][k] ,(a_i \leq j \leq b_i) \]

也就是考虑上一个选择的位置和大小转移

问题在于第二维到了\(10^9\)
这里就用到了技巧:离散化

先把点离散化,点构成了若干个新的区间

于是\(dp[i][j]\)表示选前\(i\)个数且第\(i\)个数非\(0\),落在区间\(j\)上的方案数

注意这里相当于一次性求出了区间的所有方案,因此转移方程也有所改变

转移时,若干块的数会在同意区间,因此枚举落在区间\(j\)的方案数,需要考虑第\(p + 1\)个数到\(i\)的落在区间\(j\)的方案数

这类似于从\([0,L]\)中取\(n\)个数,且所有非零数严格递增,容易证明这样的方案数是\(\tbinom{L + n}{n}\)

\(m\)表示\(p + 1\)\(i\)中能够落在区间\(j\)的个数,那么考虑这一段的组合

\[dp[i][j] = \sum_{p = 0}^{i - 1}\sum_{k = 0}^{j - 1}\tbinom{L + m - 1}{m} dp[p][k] \]

将枚举到的区间作为阶段,此时\(m\)不变,于是显然可以对\(dp[p][k]\)做前缀和

\[g[i][j] = \sum_{k =1}^{j - 1}f[i][k] \]

于是

\[dp[i][j] = \sum_{p = 0}^{i - 1} \tbinom{L + m - 1}{m} g[p][j - 1] \]

实现起来的话注意一下离散化区间的细节,还有提前递推好组合数省一个\(log\)

代码

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define re register
using namespace std;
typedef long long ll;

int rd(){
	int x = 0;
	char ch = getchar();
	while(ch < ‘0‘ || ch > ‘9‘) {
		ch = getchar();
	}
	while(ch >= ‘0‘ && ch <= ‘9‘) {
		x = x * 10 + ch - ‘0‘;
		ch = getchar();
	}
	return x;
}

const int maxn = 505;
const ll MOD = 1e9 + 7;
int num[maxn << 1],a[maxn],b[maxn],c[maxn],iv[maxn];
int g[maxn];
int tot;

int main(){
	int n = rd();
	iv[1] = 1;
	for(int i = 2;i <= n;i++) iv[i] = (ll)(MOD - MOD / i) * iv[MOD % i] % MOD;
	for(int i = 1;i <= n;i++){
		a[i] = rd();
		b[i] = rd();
		num[++tot] = a[i];
		num[++tot] = b[i] + 1;
	}
	sort(num + 1,num + tot + 1);
	for(int i = 1;i <= n;i++){
		a[i] = lower_bound(num + 1,num + 1 + tot,a[i]) - num;
		b[i] = lower_bound(num + 1,num + 1 + tot,b[i] + 1) - num;
	}
	c[0] = 1;
	g[0] = 1;
	for(int j = 1;j < tot;j++){
		int len = num[j + 1] - num[j];
		for(int i = 1;i <= n;i++) c[i] = (ll)c[i - 1] * (len + i - 1) % MOD * (iv[i] % MOD) % MOD;
		for(int i = n;i;i--){
			if(a[i] <= j && j + 1 <= b[i]) {
				int f = 0;
				int m = 1;
				int C = len;
				for(int p = i - 1;p >= 0;p--){
					f += (ll)C * g[p] % MOD;
					f %= MOD;
					if(a[p] <= j && j + 1 <= b[p]) C = c[++m];
				}
				g[i] += f;
				g[i] %= MOD;
			}
		}
	}
	int ans = 0;
	for(int i = 1;i <= n;i++)
		ans += g[i],ans %= MOD;
	printf("%d",ans);
}

APIO2016 划艇 计数DP

上一篇:windows命令行查找文件


下一篇:restfull api+Basic auth+weblogic