[Lydsy2017省隊十連測]商店購物
Description
在 Byteland一共開着 n家商店,編號依次爲 1到 n,其中編號爲1到 m的商店有日消費量上限,第 i家商店的日消費量上限爲wi。Byteasar每次購物的過程是這樣的:依次經過每家商店,然後購買非負整數價格的商品,並在結賬的時候在賬本上寫上在這家商店消費了多少錢。當然,他在這家商店也可以什麼都不買,然後在賬本上寫上一個0。這一天, Byteasar日常完成了一次購物,但是他不慎遺失了他的賬本。他只記得自己這一天一共消費了多少錢,請寫一個程序,幫助 Byteasar計算有多少種可能的賬單。
-
Input
第一行包含三個正整數
n, m, k,分別表示商店的個數、有限制的商店個數以及總消費量。
第二行包含 m個整數,依次表示 w1;w2…wm。
1 ≤ m ≤ n,0≤ wi ≤ 300,1 ≤ n, k ≤ 5000000
m<=300 -
Output
輸出一行一個整數,即可能的賬單數,由於答案可能很大,請對1000000007取模輸出。
Sample Input
3 2 8
2 1
Sample Output
6
HINT
6 種方案分別爲:
{0; 0; 8};
{1; 0; 7};
{2; 0; 6};
{0; 1; 7};
{1; 1; 6};
{2; 1; 5}。
我首先看到這道題:DP!!
對,沒錯這顯然是DP可寫的。所以我們考慮DP的寫法。
20分代碼
首先我們先做簡單的揹包:
爲前個商店總消費爲j的方案數,易得方程:
所以用 O(nk^2) 枚舉一下就可以了。
50分代碼
我們發現狀態的轉移的是上的一段連續區間,那麼我們只用搞一個前綴和。
設
即
每次求出第i天的數組值就再枚舉求出即可。
我們也可以得到DP式:
當然我們也可以動態地維護前綴和。
對於,如下(使用了滾動數組)。複雜度 O(nk)
爲前綴和。
last=now;
now^=1;
j=0;
sum=0;
while(j<=tot)
{
sum+=f[last][j];
sum%=1000000007;
if(j>w[i])
{
sum-=f[last][j-w[i]-1];
sum%=1000000007;
sum+=1000000007;
sum%=1000000007;
}
sum+=1000000007;
sum%=1000000007;
f[now][j]=sum;
++j;
}
100分代碼
由於很小,容易想到先預處理出前m家商店,再用組合數求出之後的值。
設爲前m家商店的總合。
不難看出
其中組合數可以由逆元求出。複雜度 O(mk+k+n)
AC代碼
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<iostream>
using namespace std;
long long n,m;
long long k,w[310],f[2][100600],sum=0,tot=0;
long long cc[10000100],dd[10000100],ans=0;
inline long long mul(long long x,long long y)
{
long long res=1;
while(y)
{
if(y&1) res=(res*1ll)%1000000007*(1ll*x)%1000000007;
y>>=1;
x=((x*1ll)*(1ll*x))%1000000007;
}
return res;
}
inline long long getc(long long x,long long y)
{
if(x<y) return 0;
return ((cc[x]*1ll*(dd[y]*1ll)%1000000007)*(1ll*dd[x-y]))%1000000007;
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&k);
int i=1,j=1;
while(i<=m)
{
scanf("%lld",&w[i]);
++i;
}
cc[0]=1;
i=1;
while(i<=10000000)
{
cc[i]=((cc[i-1]*1ll)%1000000007*(1ll*i))%1000000007;
++i;
}
dd[10000000]=mul(cc[10000000],1000000005);
i=10000000;
while(i)
{
dd[i-1]=((dd[i]*1ll)%1000000007*(1ll*i))%1000000007;
--i;
}
n-=m;
f[0][0]=1;
i=1;
int last,now=0;
while(i<=m)
{
tot+=w[i];
last=now;
now^=1;
j=0;
sum=0;
while(j<=tot)
{
sum+=f[last][j];
sum%=1000000007;
if(j>w[i])
{
sum-=f[last][j-w[i]-1];
sum%=1000000007;
sum+=1000000007;
sum%=1000000007;
}
sum+=1000000007;
sum%=1000000007;
f[now][j]=sum;
++j;
}
++i;
}
if(!n)
{
if(k<=tot) ans=f[now][k];
else ans=0;
}
else
{
i=min(k,tot);
while(i>=0)
{
ans+=(f[now][i]*(1ll*getc(k-i+n-1,k-i))%1000000007)%1000000007;
--i;
}
}
ans%=1000000007;
printf("%lld\n",ans);
return 0;
}
總結
是一道很好的題目,給足了暴力分,數據也引導人循序漸進。
有難度但想到正解並不難。是一道水題。