@gym - 100958J@ Hyperrectangle


@description@

给定一个大小为 \(l_1\times l_2 \dots l_d\) 的 d 维超矩形,将它的一个角放置在原点,使得它的第 i 维在范围 \([0, l_i]\)

求出这个矩形中满足 \(x_1 + x_2 + \dots + x_d \leq s\) 的点形成的体积 × d!。

原题传送门。

@solution@

首先,简单积分一下可以得到满足 \(\sum_{i=1}^{d}x_i \leq s\) 的点形成的体积为 \(\frac{s^d}{d!}\)

update in 2020/07/09:草我是哈批,做到这一步直接容斥 \(x_i\leq l_i\) 的限制就行了。也不需要下面那些乱七八糟的东西。


因为数据都是整数,我们不妨去考虑每一个 \(1\times 1 \dots \times 1\) 的单位超矩形 \((x_1, x_2, \dots x_d) - (x_1 + 1, x_2 + 1, \dots x_d + 1)\) 的贡献。

除去完全包含(\(s - \sum_{i=1}^{d}x_i \geq d\))与完全不包含(\(s - \sum_{i=1}^{d}x_i \leq 0\)),还有 d - 1 种不完全包含的情况 (\(0 < s - \sum_{i=1}^{d}x_i < d\))。

可以作个背包 + 简单前缀和优化算出每一种情况有多少个单位超矩形。

然而不完全包含的情况并不能直接通过积分算体积。不妨仍考虑组合方法,记 \(f_j\) 表示满足 \(s - \sum_{i=1}^{d}x_i = j\) 的体积。

对于 \(\sum_{i=1}^{d}x_i \leq s\),积分出来的体积是 \(\frac{s^d}{d!}\);然而我们也可以采用单位超矩形的贡献算出体积。
不妨只考虑 s < d 的情况,则可以列出等式 \(\frac{s^d}{d!} = \sum_{i=0}^{s}{s - i + d - 1\choose d - 1}f_i\),由此就可以在 \(f_s\)\(f_{0...s-1}\) 之间建立递推关系。

时间复杂度瓶颈在背包的部分,为 O(l^3)。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 300;
const int MAXS = MAXN*MAXN;
const int MOD = int(1E9) + 7;

inline int add(int x, int y) {return (x + y) % MOD;}
inline int mul(int x, int y) {return 1LL*x*y % MOD;}
inline int sub(int x, int y) {return add(x, MOD-y);}

int pow_mod(int b, int p) {
	int ret = 1;
	for(int i=p;i;i>>=1,b=mul(b,b))
		if( i & 1 ) ret = mul(ret, b);
	return ret;
}

int l[MAXN + 5], d, s, S;

int fct[2*MAXN + 5], ifct[2*MAXN + 5];
int comb(int n, int m) {
	return mul(fct[n], mul(ifct[m], ifct[n-m]));
}
int f[MAXN + 5];
void init() {
	fct[0] = 1;
	for(int i=1;i<=2*d;i++)
		fct[i] = mul(fct[i-1], i);
	ifct[2*d] = pow_mod(fct[2*d], MOD-2);
	for(int i=2*d-1;i>=0;i--)
		ifct[i] = mul(ifct[i+1], i+1);
	for(int i=1;i<=d;i++) {
//		f[i] = mul(pow_mod(i, d), ifct[d]);
		f[i] = pow_mod(i, d);
		for(int j=0;j<i;j++)
			f[i] = sub(f[i], mul(comb(i-j+d-1, d-1), f[j]));
	}
}

int g[MAXS + 5];
int main() {
	scanf("%d", &d), init();
	for(int i=1;i<=d;i++)
		scanf("%d", &l[i]), l[i]--;
	scanf("%d", &s);
	g[0] = 1;
	for(int i=1;i<=d;i++) {
		S += l[i];
		for(int j=1;j<=S;j++) g[j] = add(g[j], g[j-1]);
		for(int j=S;j>l[i];j--) g[j] = sub(g[j], g[j-l[i]-1]);
	}
	int ans = 0;
	for(int i=0;i<=s;i++)
		ans = add(ans, mul(f[min(s-i,d)], g[i]));
	printf("%d\n", ans);
}

@details@

一开始总以为它是一道积分题,结果发现怎么积分都不对劲。

然后尝试不从积分走而从组合数学走,发现还真能做出来。

posted @ 2020-03-04 14:36  Tiw_Air_OAO  阅读(237)  评论(0编辑  收藏  举报