参考自アリ本(プログラミングコンテスト チャレンジブック)
01背包
n
n
n个重
w
i
w_i
wi值
v
i
v_i
vi的物品,选总重
≤
W
\le W
≤W的组合的最大价值。
(略)
最长公共子序列(LCS)
给两个字符串(s,t),求最长公共子序列。
O ( n m ) O(nm) O(nm)
int n,m;
char s[MAX_N],t[MAX_M]; // inputs
int dp[MAX_N + 1][MAX_M + 1]; // DP
void solve(){
for(int i=0;i<n;i++){
for(int j=0;j<0;j++){
if(s[i]==y[j])
dp[i+1][j+1]=dp[i][j]+1;
else
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
}
}
printf("%d\n",dp[n][m]);
}
最长上升子序列(LIS)
长 n n n数列 a n a_n an。求最长上升子序列。
O ( n log n ) O(n\log{n}) O(nlogn)
d
p
[
i
]
:
=
dp[i]:=
dp[i]:=所有长度为
i
+
1
i+1
i+1的上升子列之中末尾元素最小值。
注意:最终算出来的
d
p
dp
dp数组并没有存这个LIS是什么,只能用来算LIS的长度。
int dp[MAXN];
void solve(){
fill(dp,dp+n,INF);
for(int i=0;i<n;i++){
*lower_bound(dp,dp+n,a[i])=a[i];
}
printf("%d\n",lower_bound(dp,dp+n,INF)-dp);
}
多重部分和
n n n个不同数字 a i a_i ai,每种 m i m_i mi个,能否选出正好和为 K K K的组合。
O ( n K ) O(nK) O(nK)
d p [ i + 1 ] [ j ] : = dp[i+1][j]:= dp[i+1][j]:=区间 [ 0 , i ] [0,i] [0,i]加到 K K K的 a i a_i ai的剩余,无法得到 K K K的话设为 − 1 -1 −1
// inputs
int dp[MAXK + 1];
void solve(){
memset(dp,-1,sizeof(dp));
dp[0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=k;j++){
if(dp[j]>=0)
dp[j]=m[i];
else if(j<a[i] || dp[j-a[i]]<=0)
dp[j]=-1;
else
dp[j]=dp[j-a[i]]-1;
}
}
if(dp[k] >= 0) printf("Yes\n");
else printf("No\n");
}
多重集组合数
n n n个不同数字,每种 a i a_i ai个,从中取 m m m个的组合数。
O ( n m ) O(nm) O(nm)
d
p
[
i
+
1
]
[
j
]
:
=
dp[i+1][j]:=
dp[i+1][j]:=从前
i
i
i钟物品中取
j
j
j个的组合数
d
p
[
i
+
1
]
[
j
]
=
d
p
[
i
+
1
]
[
j
+
1
]
+
d
p
[
i
]
[
j
]
−
d
p
[
i
]
[
j
−
1
−
a
i
]
dp[i+1][j] = dp[i+1][j+1] + dp[i][j]-dp[i][j-1-a_i]
dp[i+1][j]=dp[i+1][j+1]+dp[i][j]−dp[i][j−1−ai]
int n,m;
int a[MAXN];
int dp[MAXN+1][MAXN+1];
void solve(){
for(int i=0;i<=n;i++) dp[i][0]=1;
for(int i=0;i<n;i++)
for(int j=1;j<=m;j++)
if(j-1-a[i]>=0) dp[i+1][j]=dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]; //Add MOD! otherwise it will <0
else dp[i+1][j]=dp[i+1][j-1]+dp[i][j];
}
划分数&狭义划分数
把数m划分成不超过n个数的和,求划分组合。
定义
划分数: H n m H_n^m Hnm
狭义划分数: h n m h_n^m hnm
定义狭义划分数为数m划分成正好n个数的和的组合数
这里我们有
H
n
m
=
∑
k
=
1
n
h
k
m
H_n^m=\sum_{k=1}^n{h_k^m}
Hnm=k=1∑nhkm
h
1
m
=
H
1
m
h_1^m=H_1^m
h1m=H1m
h
n
m
=
H
n
m
−
H
n
−
1
m
,
(
n
≥
2
)
h_n^m=H_n^m-H_{n-1}^m,(n\ge2)
hnm=Hnm−Hn−1m,(n≥2)
且
H
n
m
=
H
n
m
−
n
+
H
n
−
1
m
(
m
−
n
≥
0
,
n
≥
1
)
H_n^m=H_n^{m-n}+H_{n-1}^m \quad (m-n\ge0,n\ge1)
Hnm=Hnm−n+Hn−1m(m−n≥0,n≥1)
H
n
m
=
H
n
−
1
m
(
m
−
n
<
0
,
n
≥
1
)
H_n^m=H_{n-1}^m \quad (m-n<0,n\ge1)
Hnm=Hn−1m(m−n<0,n≥1)
H
n
0
=
1
,
H
0
m
=
0
(
n
≥
0
,
m
≥
1
)
H_n^0=1,H_0^m=0 \quad (n\ge 0,m\ge 1)
Hn0=1,H0m=0(n≥0,m≥1)
同时
h
n
m
=
H
n
m
−
n
h_n^m=H_n^{m-n}
hnm=Hnm−n
H n m = ∑ k = 1 m h k m = H n m + ∑ k = 1 m − 1 h k m = H n m − n + H n − 1 m H_n^m=\sum_{k=1}^m{h_k^m}=H_n^m+\sum_{k=1}^{m-1}{h_k^m}=H_n^{m-n}+H_{n-1}^m Hnm=k=1∑mhkm=Hnm+k=1∑m−1hkm=Hnm−n+Hn−1m
O ( m n ) O(mn) O(mn)
// inputs
int n,m;
int dp[MAXM+1][MAXN+1];
void solve(){
dp[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<=n;j++){
if(j-i>=0)
dp[i][j]=dp[i-1][j]+dp[i][j-i];
else
dp[i][j]=dp[i-1][j];
}
}
printf("%d\n",dp[m][n]);
}