1. 程式人生 > >[BZOJ 5215] [Lydsy2017省隊十連測]商店購物

[BZOJ 5215] [Lydsy2017省隊十連測]商店購物

題目描述

在 Byteland一共開著 nn 家商店,編號依次為 11nn,其中編號為 11mm 的商店有日消費量上限,第 ii 家商店的日消費量上限為wiw_i。Byteasar每次購物的過程是這樣的:依次經過每家商店,然後購買非負整數價格的商品,並在結賬=的時候在賬本上寫上在這家商店消費了多少錢。當然,他在這家商店也可以什麼都不買,然後在賬本上寫上一個00。這一天, Byteasar日常完成了一次購物,但是他不慎遺失了他的賬本。他只記得自己這一天一共消費了多少錢,請寫一個程式,幫助 Byteasar計算有多少種可能的賬單。

輸入輸出格式

輸入格式:

第一行包含三個正整數 n

,m,kn, m, k,分別表示商店的個數、有限制的商店個數以及總消費量。

第二行包含 mm 個整數,依次表示 w1,w2...wmw_1,w_2...w_m

輸出格式:

輸出一行一個整數,即可能的賬單數,由於答案可能很大,請對10000000071000000007取模輸出。

輸入輸出樣例

輸入樣例#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}

1mn1 ≤ m ≤ n0wi3000≤ w_i ≤ 3001n

,k50000001 ≤ n, k ≤ 5000000m300m\leq 300

解題分析

很顯然前半部分dpdp搞, 後半部分組合數搞。

先來看前半部分: 我們很容易想到一個O(N4)O(N^4)dpdp : 設dp[i]dp[i]為前tt個點總共取到ii的方案數, 那麼dp[i]=j=ival[t+1]idp[i]dp[i]=\sum_{j=i-val[t+1]}^{i}dp[i], 但顯然沒有分。

再仔細觀察, 發現能轉移到一個點的值為字首的字尾, 因此我們每次做一次字首和, 就可以O(N2)O(N^2)

轉移, 這樣總的複雜度就是O(N3)O(N^3)

再來看後面的一部分, 列舉前面取到的值xx, 相當於我們要求kxk-x塊錢分到nmn-m個店裡, 且允許某個店不分錢的方案數。 相當於nm1n-m-1個板插到kxk-x塊錢中間的方案數, 所以答案為(nm+kx1nm1)\binom{n-m+k-x-1}{n-m-1}

另外, 這道題十分卡常, 取模用減的方法, 不要全部開longlonglong\ long計算, 線篩階乘逆元可以先打表得到1000000010000000的逆元再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);
}