[BZOJ 5215] [Lydsy2017省队十连测]商店购物

122 篇文章 1 订阅
22 篇文章 0 订阅
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 1mn 0 ≤ w i ≤ 300 0≤ w_i ≤ 300 0wi300 1 ≤ n , k ≤ 5000000 1 ≤ n, k ≤ 5000000 1n,k5000000 m ≤ 300 m\leq 300 m300

解题分析

很显然前半部分 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=ival[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 kx块钱分到 n − m n-m nm个店里, 且允许某个店不分钱的方案数。 相当于 n − m − 1 n-m-1 nm1个板插到 k − x k-x kx块钱中间的方案数, 所以答案为 ( n − m + k − x − 1 n − m − 1 ) \binom{n-m+k-x-1}{n-m-1} (nm1nm+kx1)

另外, 这道题十分卡常, 取模用减的方法, 不要全部开 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值