题解 划艇

传送门

于是我开始补n年前的题了
好题,见到想不到,想到不敢写,敢写调不对

好题!
首先可以想到一个简单的DP,令\(dp[i][j]\)为前i所学校艇数不超过j时方案数
然而发现\(j \in 1e9\)而且还没有部分分,那怎么处理呢?
可以想到离散化,然而离散化后这题好像完全没法转移
于是重点来了:在离散化后的值域上进行方案数DP

这题就当例题吧
我们把权值离散化之后会出现许多个小区间
这里有个很重要的细节:离散化权值的时候要将区间写成\([a_i, b_i+1)\)的形式再离散化,而不能直接把\(a_i, b_i\)扔进去离散化
原因是个小边界问题:在枚举区间时,我们枚举的实际上是区间\([uni_i, uni_{i+1})\)
而当出现一个区间实际上是\([uni_i, uni_i]\)时,我们这样枚举就会漏掉它
所以可以把\([a_i, b_i+1)\)扔进去离散化

然后考虑如何转移
发现当我们转移到学校\(i\),区间\(j\)时,在i前面的所有学校\(p\)只有(原范围)能取到\(j\)和不能取到\(j\)两种
而且如果取不到\(j\),在转移\(dp_{i,j}\)时学校\(p\)只能取小于\(j\)的区间,再乘上\(len\)就是方案数
如果能取到,就需要考虑在区间\(j\)内如何取了

  • 引理:在区间\([0, len]\)内取\(m\)个单调上升(可以重复选0)的数的方案数是\(\binom{len+m}{m}\)
    证明:就是加进去\(m\)个可选的0

所以令学校p+i~i间有\(m\)个可以取到区间j
那对于学校\(p\),如果它不取区间\(j\),它后面那部分学校的方案数就是\(\binom{len+m-1}{m}\),这里减一是因为第i所学校必须选
直接乘上\(dp_{p,k}\)即可,这里\(k\)也需要枚举
所以这一部分式子写出来是

\[dp_{i,j} = \sum\limits_{p=1}^{i-1} \binom{len+m-1}{m} \sum\limits_{k=1}^{j-1}dp_{p,k} \]

  • 组合数部分可以用一个公式 \(C_n^m = C_{n-1}^{m-1}*\frac{n}{m}\) 推出来

最后面那个求和可以前缀和优化
时间复杂度\(O(n^3)\)
具体实现上细节非常多

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 550
#define ll long long 
#define int long long 

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline ll read() {
	ll ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n;
ll a[N], b[N], uni[N<<1], usize, dp[N], inv[N];
const ll mod=1e9+7;

signed main()
{
	n=read();
	for (int i=1; i<=n; ++i) a[i]=read(), b[i]=read()+1, uni[++usize]=a[i], uni[++usize]=b[i];
	sort(uni+1, uni+usize+1);
	usize=unique(uni+1, uni+usize+1)-uni-1;
	//cout<<"usize: "<<usize<<endl;
	//cout<<"uni: "; for (int i=1; i<=usize; ++i) cout<<uni[i]<<' '; cout<<endl;
	for (int i=1; i<=n; ++i) a[i]=lower_bound(uni+1, uni+usize+1, a[i])-uni;
	for (int i=1; i<=n; ++i) b[i]=lower_bound(uni+1, uni+usize+1, b[i])-uni;
	inv[0]=inv[1]=1;
	for (int i=2; i<N; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	
	dp[0]=1;
	for (int j=1; j<usize; ++j) {
		//cout<<"now j="<<j<<" and uni="<<uni[j]<<' '<<uni[j+1]<<endl;
		ll len=(uni[j+1]-uni[j])%mod, c, m;
		for (int i=n; i; --i) if (a[i]<=j && b[i]>=j+1) {
			//cout<<"now i="<<i<<endl;
			c=len, m=1;
			for (int p=i-1; ~p; --p) {
				//cout<<"now p="<<p<<endl;
				//printf("dp[%d][%d]=(%lld+%lld*dp[%d][%d](%lld) = %lld\n", i, j, dp[i][j], c, p, j-1, dp[p][j-1], dp[i][j]+c*dp[p][j-1]);
				dp[i]=(dp[i]+c*dp[p]%mod)%mod;
				//cout<<"cmp: "<<a[p]<<' '<<b[p]<<' '<<uni[j]<<' '<<uni[j+1]<<endl;
				if (a[p]<=j && b[p]>=j+1) {++m; c=c*(len+m-1)%mod*inv[m]%mod; /*cout<<"so now C="<<c<<endl;*/}
			}
			#if 0
			cout<<"sum: "<<dp[i][j]<<' '<<dp[i][j-1]<<endl;
			dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
			cout<<"add: "<<dp[i][j]<<endl;
			#endif
		}
		//for (int i=0; i<=n; ++i) dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
		//cout<<endl;
	}
	
	ll ans=0;
	for (int i=1; i<=n; ++i) ans=(ans+dp[i])%mod;
	printf("%lld\n", ans);
	
	return 0;
}
posted @ 2021-08-01 20:46  Administrator-09  阅读(14)  评论(0编辑  收藏  举报