BZOJ传送门
题目描述
在 Byteland一共开着 n n n 家商店,编号依次为 1 1 1 到 n n n,其中编号为 1 1 1 到 m m m 的商店有日消费量上限,第 i i i 家商店的日消费量上限为 w i w_i wi。Byteasar每次购物的过程是这样的:依次经过每家商店,然后购买非负整数价格的商品,并在结账=的时候在账本上写上在这家商店消费了多少钱。当然,他在这家商店也可以什么都不买,然后在账本上写上一个 0 0 0。这一天, Byteasar日常完成了一次购物,但是他不慎遗失了他的账本。他只记得自己这一天一共消费了多少钱,请写一个程序,帮助 Byteasar计算有多少种可能的账单。
输入输出格式
输入格式:
第一行包含三个正整数 n , m , k n, m, k n,m,k,分别表示商店的个数、有限制的商店个数以及总消费量。
第二行包含 m m m 个整数,依次表示 w 1 , w 2 . . . w m w_1,w_2...w_m w1,w2...wm。
输出格式:
输出一行一个整数,即可能的账单数,由于答案可能很大,请对 1000000007 1000000007 1000000007取模输出。
输入输出样例
输入样例#1:
3 2 8
2 1
输出样例#1:
6
说明
6 种方案分别为:{0; 0; 8}; {1; 0; 7}; {2; 0; 6}; {0; 1; 7}; {1; 1; 6}; {2; 1; 5}
。
1 ≤ m ≤ n 1 ≤ m ≤ n 1≤m≤n, 0 ≤ w i ≤ 300 0≤ w_i ≤ 300 0≤wi≤300, 1 ≤ n , k ≤ 5000000 1 ≤ n, k ≤ 5000000 1≤n,k≤5000000, m ≤ 300 m\leq 300 m≤300。
解题分析
很显然前半部分 d p dp dp搞, 后半部分组合数搞。
先来看前半部分: 我们很容易想到一个 O ( N 4 ) O(N^4) O(N4)的 d p dp dp : 设 d p [ i ] dp[i] dp[i]为前 t t t个点总共取到 i i i的方案数, 那么 d p [ i ] = ∑ j = i − v a l [ t + 1 ] i d p [ i ] dp[i]=\sum_{j=i-val[t+1]}^{i}dp[i] dp[i]=∑j=i−val[t+1]idp[i], 但显然没有分。
再仔细观察, 发现能转移到一个点的值为前缀的后缀, 因此我们每次做一次前缀和, 就可以 O ( N 2 ) O(N^2) O(N2)转移, 这样总的复杂度就是 O ( N 3 ) O(N^3) O(N3)。
再来看后面的一部分, 枚举前面取到的值 x x x, 相当于我们要求 k − x k-x k−x块钱分到 n − m n-m n−m个店里, 且允许某个店不分钱的方案数。 相当于 n − m − 1 n-m-1 n−m−1个板插到 k − x k-x k−x块钱中间的方案数, 所以答案为 ( n − m + k − x − 1 n − m − 1 ) \binom{n-m+k-x-1}{n-m-1} (n−m−1n−m+k−x−1)。
另外, 这道题十分卡常, 取模用减的方法, 不要全部开 l o n g l o n g long\ long long long计算, 线筛阶乘逆元可以先打表得到 10000000 10000000 10000000的逆元再 O ( N ) O(N) O(N)推回去。 详见代码:
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <cctype>
#include <cstdlib>
#include <cstring>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define File freopen("shopping.in", "r", stdin), freopen("shopping.out", "w", stdout)
#define MX 100500
#define max(a, b) ((a) > (b) ? (a) : (b))
#define SIZ 10000500
#define MOD 1000000007
#define ll long long
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
template <class T> IN void mod(T &x, int add) {x += add; if(x >= MOD) x -= MOD;}
int dp[SIZ], inv[SIZ], fac[SIZ], ans;
int n, m, goal;
int val[MX];
IN int C(R int n, R int m)
{
if(n < 0) return 1;
if(m < 0) return 0;
return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int main(void)
{
File;
in(n), in(m), in(goal); dp[0] = 1;
R int i, j; int bd = 300 * m, tar, sum = 0;
for (i = 1; i <= m; ++i) in(val[i]);
for (i = 1; i <= m; ++i)
{
sum += val[i]; int v = val[i] + 1;
for (j = sum - val[i]; ~j; --j) mod(dp[j + v], MOD - dp[j]);
for (j = 1; j <= bd; ++j) mod(dp[j], dp[j - 1]);
}
inv[10000000] = 979208068;
bd = goal + n - m; inv[0] = inv[1] = fac[0] = 1;
for (i = 9999999; i; --i) inv[i] = 1ll * inv[i + 1] * (i +1) % MOD;
for (i = 1; i <= bd; ++i) fac[i] = 1ll * fac[i - 1] * i % MOD;
for (i = 0; i <= goal; ++i)
(ans += 1ll * dp[i] * C(goal - i + n - m - 1, n - m - 1) % MOD) %= MOD;
printf("%d", ans);
}