Live2D

Solution -「APIO 2016」「洛谷 P3643」划艇

\(\mathcal{Description}\)

  Link & 双倍经验.

  给定 \(n\) 个区间 \([a_i,b_i)\)注意原题是闭区间,这里只为方便后文描述),求 \(\{c_n\}\) 的个数,使得:

  • \(\forall i~~~~c_i=0\lor c_i\in[a_i,b_i)\)
  • \(\forall i<j~~~~c_i\not=0\land c_j\not=0\Rightarrow c_i<c_j\)

  对 \(10^9+7\) 取模。

  \(n\le500\)\(1\le a_i\le b_i\le10^9\)

\(\mathcal{Solution}\)

  一个很 naive 的 DP 想法,\(f(i,j)\) 表示考虑前 \(i\) 个位置,\(c_i=j~(j\not=0)\) 时的方案数。问题在于第二维开销过大,考虑离散化所有端点坐标。

  先来一个引理,取值在 \([a,b)\),长度为 \(n\) 的上升整数序列的个数为 \(\binom{b-a}{n}\),显然选 \(n\) 个数就可以了。

  再来一个引理,取值在 \([a,b)\cup\{0\}\),长度为 \(n\),非零的位置是上升整数序列的序列个数为 \(\binom{b-a+n}{n}\),证明也很显然,有几个 \(0\) 可以选,虽然不同的 \(0\) 可以任意排列,但看上去都是一样的。所以钦定 \(0\) 的大小关系后就等价于令区间为 \([a-n,b]\),长度为 \(n\) 时的上一引理。

  接着刚才的思路,离散化时,排过序的端点们把坐标轴分为若左闭右开的区间,从左开始第 \(t\) 个区间称作第 \(t\) 段。令 \(f(i,j)\) 表示考虑前 \(i\) 个位置,\(c_i\) 属于\(j\)时的方案数。设 \([j,j+1)\) 实际映射 \([a,b)\),枚举 \(k<i\),转移:

\[f(i,j)\leftarrow f(i,j)+f(k,j-1)\binom{b-a+x-1}{x},~~~~\text{where }x=1+\sum_{t=k+1}^{j-1}[j\in[a_t,b_t)] \]

  组合数运用了引理二。注意钦定 \(c_i\) 不为 \(0\),所以上面 \(-1\)

  扫 \(f\) 就结束了,交换枚举顺序,第二维还可以滚掉。复杂度 \(\mathcal O(n^3)\)

\(\mathcal{Code}\)

/* Clearink */

#include <cstdio>
#include <algorithm>

const int MAXN = 500, MOD = 1e9 + 7;
int n, a[MAXN + 5], b[MAXN + 5], tmp[MAXN * 2 + 5], inv[MAXN + 5];
int f[MAXN + 5], comb[MAXN + 5];

inline int mul ( long long a, const int b ) { return a * b % MOD; }
inline int& addeq ( int& a, const int b ) { return ( a += b ) < MOD ? a : a -= MOD; }

int main () {
	scanf ( "%d", &n ), inv[1] = 1;
	for ( int i = 1; i <= n; ++ i ) {
		if ( i > 1 ) inv[i] = mul ( MOD - MOD / i, inv[MOD % i] );
		scanf ( "%d %d", &a[i], &b[i] ), ++ b[i];
		tmp[2 * i - 1] = a[i], tmp[i << 1] = b[i];
	}
	std::sort ( tmp + 1, tmp + ( n << 1 | 1 ) );
	int m = std::unique ( tmp + 1, tmp + ( n << 1 | 1 ) ) - tmp - 1;
	for ( int i = 1; i <= n; ++ i ) {
		a[i] = std::lower_bound ( tmp + 1, tmp + m + 1, a[i] ) - tmp;
		b[i] = std::lower_bound ( tmp + 1, tmp + m + 1, b[i] ) - tmp;
	}
	f[0] = 1;
	for ( int j = 1, len; j < m; ++ j ) {
		len = tmp[j + 1] - tmp[j], comb[0] = 1;
		for ( int i = 1; i <= n; ++ i ) {
			comb[i] = mul ( mul ( comb[i - 1], len + i - 1 ), inv[i] );
		}
		for ( int i = n; i; -- i ) {
			if ( j < a[i] || b[i] <= j ) continue;
			for ( int k = i - 1, c = 1; ~k; -- k ) {
				addeq ( f[i], mul ( comb[c], f[k] ) );
				if ( a[k] <= j && j < b[k] ) ++ c;
			}
		}
	}
	int ans = 0;
	for ( int i = 1; i <= n; ++ i ) addeq ( ans, f[i] );
	printf ( "%d\n", ans );
	return 0;
}

\(\mathcal{Details}\)

  目前洛谷最优解,兔的代码吸口氧真的快到飞起 www。

posted @ 2020-10-15 21:39  Rainybunny  阅读(179)  评论(0)    收藏  举报