DP问题汇总,快乐背包背一背,小学算数数一数

参考自アリ本(プログラミングコンテスト チャレンジブック)


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][j1ai]

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=1nhkm
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=HnmHn1m,(n2)


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=Hnmn+Hn1m(mn0,n1)
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=Hn1m(mn<0,n1)
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(n0,m1)

同时
h n m = H n m − n h_n^m=H_n^{m-n} hnm=Hnmn

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=1mhkm=Hnm+k=1m1hkm=Hnmn+Hn1m

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]);
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值