(DP+組合數)[Lydsy2017省隊十連測]商店購物

[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分代碼

首先我們先做簡單的揹包:
 fi,j\ f_{i,j}爲前個商店總消費爲j的方案數,易得方程:
fi,j=k=jwijfi1,k&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;,i&lt;=m f_{i,j}= \sum_{k=j-w_{i}}^{j} f_{i-1,k}\,\,\,\,\,\,\,\,\,\,\,\,,i&lt;=m

fi,j=k=0jfi1,k&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;,m&lt;i&lt;=n f_{i,j}=\sum_{k=0}^{j}f_{i-1,k}\,\,\,\,\,\,\,\,\,\,\,\,,m&lt;i&lt;=n
所以用 O(nk^2) 枚舉一下就可以了。

50分代碼

我們發現狀態的轉移的是 fi1\ f_{i-1}上的一段連續區間,那麼我們只用搞一個前綴和。
 si,j\ s_{i,j}
si,j=k=0jfi,k s_{i,j}=\sum_{k=0}^{j}f_{i,k}

si,j=si,j1+fi,j s_{i,j}=s_{i,j-1}+f_{i,j}
每次求出第i天的 fi,j\ f_{i,j}數組值就再枚舉求出 si,j\ s_{i,j}即可。
我們也可以得到DP式:
fi,j=si1,jsi1,jwi1&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;,i&lt;=m f_{i,j}=s_{i-1,j}-s_{i-1,j-w_{i}-1}\,\,\,\,\,\,\,\,\,\,\,\,,i&lt;=m

fi,j=si1,j&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;,m&lt;i&lt;=n f_{i,j}=s_{i-1,j}\,\,\,\,\,\,\,\,\,\,\,\,,m&lt;i&lt;=n
當然我們也可以動態地維護前綴和。
對於 1m\ 1-m,如下(使用了滾動數組)。複雜度 O(nk)
 sum\ sum爲前綴和。

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很小,容易想到先預處理出前m家商店,再用組合數求出之後的值。
 tot\ tot爲前m家商店的 wi\ w_{i}總合。
不難看出
ans=j=0min(k,tot)fm,j(kj+n1kj) ans=\sum_{j=0}^{min(k,tot)}f_{m,j}*\binom{k-j+n-1}{k-j}
其中組合數可以由逆元求出。複雜度 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;
}

總結

是一道很好的題目,給足了暴力分,數據也引導人循序漸進。
有難度但想到正解並不難。是一道水題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章