题目大意:
有
n
n
n 种物品,第
i
i
i 种物品有
a
i
a_i
ai 个,不同种类的物品可以相互区分但是同一种的物品无法相互区分
求从这些物品种取出
m
m
m 个物品有多少种取法
题目分析:
为了避免重复计数,我们对一种物品尽量一次性拿完,故定义状态:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为前
i
i
i 种物品取出了
j
j
j 个物品的方案数
考虑转移,前
i
i
i 种物品取出了
j
j
j 个物品这个状态可以由前
i
−
1
i-1
i−1 种物品取出了
j
−
k
j-k
j−k 个物品再从第
i
i
i 种物品种选取
k
k
k 个物品转移过来,故有转移方程:
d
p
[
i
+
1
]
[
j
]
=
∑
k
=
0
m
i
n
(
j
,
a
i
)
d
p
[
i
]
[
j
−
k
]
dp[i+1][j]=\sum_{k=0} ^{min(j,a_i)} dp[i][j-k]
dp[i+1][j]=k=0∑min(j,ai)dp[i][j−k]
此转移方程的时间复杂度显然为
O
(
n
∗
m
2
)
O(n*m^2)
O(n∗m2) ,复杂度不太优秀
那么继续考虑优化,我们发现求和上界 m i n ( j , a i ) min(j,a_i) min(j,ai) 可以分为两种情况:
-
j
≤
a
i
j \le a_i
j≤ai ,将求和公式部分展开取出
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 再化成求和公式的形式
∑ k = 0 m i n ( j , a i ) d p [ i ] [ j − k ] \sum_{k=0} ^{min(j,a_i)} dp[i][j-k] ∑k=0min(j,ai)dp[i][j−k]
= ∑ k = 0 j d p [ i ] [ j − k ] = \sum_{k=0} ^j dp[i][j-k] =∑k=0jdp[i][j−k]
= d p [ i ] [ j ] + d p [ i ] [ j − 1 ] + . . . + d p [ i ] [ 0 ] = dp[i][j]+dp[i][j-1]+...+dp[i][0] =dp[i][j]+dp[i][j−1]+...+dp[i][0]
= d p [ i ] [ j ] + ∑ k = 0 j − 1 d p [ i ] [ j − 1 − k ] = dp[i][j]+\sum_{k=0} ^{j-1} dp[i][j-1-k] =dp[i][j]+∑k=0j−1dp[i][j−1−k]
将 ∑ k = 0 j − 1 d p [ i ] [ j − 1 − k ] \sum_{k=0} ^{j-1} dp[i][j-1-k] ∑k=0j−1dp[i][j−1−k] 带回状态转移方程有
∑ k = 0 j − 1 d p [ i ] [ j − 1 − k ] \sum_{k=0} ^{j-1} dp[i][j-1-k] ∑k=0j−1dp[i][j−1−k]
= ∑ k = 0 j − 1 , a I d p [ i ] [ ( j − 1 ) − k ] = \sum_{k=0} ^{j-1,a_I} dp[i][(j-1)-k] =∑k=0j−1,aIdp[i][(j−1)−k]
= d p [ i + 1 ] [ j − 1 ] = dp[i+1][j-1] =dp[i+1][j−1]
故在此时有 d p [ i + 1 ] [ j ] = d p [ i ] [ j ] + d p [ i + 1 ] [ j − 1 ] dp[i+1][j] = dp[i][j]+dp[i+1][j-1] dp[i+1][j]=dp[i][j]+dp[i+1][j−1] -
j
>
a
i
j \gt a_i
j>ai ,将求和公式部分展开取出
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 和
d
p
[
i
]
[
j
−
1
−
a
i
]
dp[i][j-1-a_i]
dp[i][j−1−ai] 再化成求和公式的形式(为了方便推导增加式子
d
p
[
i
]
[
j
−
1
−
a
[
i
]
]
−
d
p
[
i
]
[
j
−
1
−
a
[
i
]
]
dp[i][j-1-a[i]] - dp[i][j-1-a[i]]
dp[i][j−1−a[i]]−dp[i][j−1−a[i]],因为
j
>
a
i
j \gt a_i
j>ai 故在
j
−
1
−
a
i
j-1-a_i
j−1−ai 时才会为0所以数组不会越界)
∑ k = 0 m i n ( j , a i ) d p [ i ] [ j − k ] \sum_{k=0} ^{min(j,a_i)} dp[i][j-k] ∑k=0min(j,ai)dp[i][j−k]
= ∑ k = 0 a i d p [ i ] [ j − k ] = \sum_{k=0} ^{a_i} dp[i][j-k] =∑k=0aidp[i][j−k]
= d p [ i ] [ j ] + d p [ i ] [ j − 1 ] + . . . + d p [ i ] [ j − a i ] = dp[i][j]+dp[i][j-1]+...+dp[i][j-a_i] =dp[i][j]+dp[i][j−1]+...+dp[i][j−ai]
= d p [ i ] [ j ] + d p [ i ] [ j − 1 ] + . . . + d p [ i ] [ j − a i ] + d p [ i ] [ j − 1 − a i ] − d p [ i ] [ j − a i − 1 ] = dp[i][j]+dp[i][j-1]+...+dp[i][j-a_i]+dp[i][j-1-a_i]-dp[i][j-a_i-1] =dp[i][j]+dp[i][j−1]+...+dp[i][j−ai]+dp[i][j−1−ai]−dp[i][j−ai−1]
取出 d p [ i ] [ j ] dp[i][j] dp[i][j] 和 − d p [ i ] [ j − 1 − a i ] -dp[i][j-1-a_i] −dp[i][j−1−ai] 对剩下的求和带入转移方程有:
d p [ i ] [ j − 1 ] + d p [ i ] [ j − 2 ] + . . . + d p [ i ] [ j − 1 − a i ] dp[i][j-1]+dp[i][j-2]+...+dp[i][j-1-a_i] dp[i][j−1]+dp[i][j−2]+...+dp[i][j−1−ai]
= ∑ k = 0 a i d p [ i ] [ ( j − 1 ) + k ] =\sum_{k=0}^{a_i}dp[i][(j-1)+k] =∑k=0aidp[i][(j−1)+k]
= d p [ i + 1 ] [ j − 1 ] =dp[i+1][j-1] =dp[i+1][j−1]
故在此时有 d p [ i + 1 ] [ j ] = d p [ i ] [ j ] + d p [ i + 1 ] [ j − 1 ] − d p [ i ] [ j − 1 − a i ] dp[i+1][j] = dp[i][j] + dp[i+1][j-1] - dp[i][j-1-a_i] dp[i+1][j]=dp[i][j]+dp[i+1][j−1]−dp[i][j−1−ai]
综合上述两种情况转移方程可以化简为:
if(j <= a[i]) dp[i][j] = dp[i-1][j]+dp[i][j-1]
else dp[i][j] = dp[i-1][j]+dp[i][j-1]+dp[i-1][j-1-a[i]]
此方程复杂度为
O
(
n
m
)
O(nm)
O(nm)
再思考状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 其空间复杂度为
O
(
n
m
)
O(nm)
O(nm) ,我们发现在转移时我们只关心这一行和上一行的元素而不关心其他行的元素故可以对第一维
i
m
o
d
2
i \ mod \ 2
i mod 2 ,空间复杂度就能优化到
O
(
m
)
O(m)
O(m)
例题:poj-3046Ant Counting
题目链接:点击这里
具体细节见代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int mod = 1e6;
int id,ans,dp[2][100005],x[1005];
int main()
{
int t = read(),a = read(),s = read(),b = read();
for(int i = 1;i <= a;i++)
{
id = read();
x[id]++;
}
dp[0][0] = dp[1][0] = 1;
for(int i = 1;i <= t;i++)
for(int j = 1;j <= b;j++)
{
if(j <= x[i])
dp[i%2][j] = (dp[(i-1)%2][j]+dp[i%2][j-1])%mod;
else
dp[i%2][j] = (dp[(i-1)%2][j]+dp[i%2][j-1]-dp[(i-1)%2][j-1-x[i]]+mod)%mod;
}
for(int i = s;i <= b;i++)
ans = (ans+dp[t%2][i])%mod;
printf("%d\n",ans);
return 0;
}