dp算法模板

01背包(物品只取一次)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
int dp[1000][1000];
typedef struct
{
	int w, v;
}S;
S s[1003];
int main()
{
	ios::sync_with_stdio(false);
	cin >> n>>m;
	for (int i = 1; i <= n; i++)
	{
		cin >> s[i].v >> s[i].w;
	}
	memset(dp, 0, sizeof(dp));
	for (int j = m; j >= 0; j--)
	{
		if (j >= s[1].w)dp[j] = s[1].v;
		else dp[j] = 0;
	}
	for (int i = 2; i <= n; i++)
	{
		for (int j = m; j >= 0; j--)
		{
		if (j >= s[i].w)dp[j] = max(dp[j], dp[j - s[i].w] + s[i].v);
		}
	}cout << dp[m] << endl;
	return 0;
}

完全背包(物品可取无限次)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, v;
int dp[1003];
typedef struct
{
	int v, w;
}S;
S s[1003];
int main()
{
	//若题目中需要将背包填充满,则将dp数组初始化为-1
	//,再初始化dp【0】=0(根据情况而定),状态转移时,
	//先判断不为-1,再转移
	ios::sync_with_stdio(false);
	cin >> n >> v;
	for (int i = 1; i <= n; i++)
	{
		cin >> s[i].v >> s[i].w;
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = s[i].w; j <= v; j++)
		{
			dp[j] = max(dp[j], dp[j - s[i].w] + s[i].v);
		}
	}cout << dp[v] << endl;
	return 0;
}

多重背包(每件物品可取p[i]次)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
int c[1003]; //花费
int v[1003]; //价值
int a[1003]; //数量
int dp[100005];
#define M 100000000 //所有物品可能的最大价值
void zeropack(int cost, int value) //01背包
{
	for (int i = m; i >= cost; i--)
	{
		dp[i] = max(dp[i], dp[i - cost] + value);
	}
}
void completepack(int cost, int value) //完全背包
{
	for (int i = cost; i <= m; i++)
	{
		dp[i] = max(dp[i], dp[i - cost] + value);
	}
}
void mutiplepack(int cost, int value, int amount) 
//多重背包,用二进制转化为01和完全求解
{
	int k;
	if (cost * amount >= m) //完全
	{
		completepack(cost, value);
		return;
	}
	k = 1;
	while (k < amount) //01
	{
		zeropack(k * cost, k * value);
		amount -= k;
		k <<= 1;
	}
	zeropack(amount * cost, amount * value);
}
int main()
{
	ios::sync_with_stdio(false);
	while (cin >> n >> m && (n && m))
	{
		for (int i = 1; i <= n; i++)cin >> c[i];
		for (int i = 1; i <= n; i++)cin >> v[i];
		for (int i = 1; i <= n; i++)cin >> a[i];
		//两种不同的初始化方式,自行选择
		memset(dp, 0, sizeof(dp));//只希望价格尽量大
		//memset(dp,-M,sizeof(dp));
		// dp[0]=0; //要求恰好装满背包
		for (int i = 1; i <= n; i++)
		{
			mutiplepack(c[i], v[i], a[i]);
		}
		cout << dp[m] << endl;
	}
	return 0;
}

二位费用背包

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 10000
int v[N];
int w[N];
int g[N];
int dp[N];
int n,ww,gg;
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> v[i] >> w[i] >> g[i];
	for (int i = 1; i <= n; i++)
	{
		for (int j = ww; j >= w[i]; j--)
		{
			for (int k = gg; k >= g[i]; k--)
			{
		dp[j][k] = max(dp[j][k], dp[j - w[i]][k - g[i]] + v[i]);
			}
		}
	}cout << dp[ww][gg] << endl;
	return 0;
}

分组背包

//【分组背包】
//有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。
//这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。
//求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,
//且价值总和最大。
//【解题思路】和多重背包有一些类似,多重背包是每个物品有si件,
//可以选0,1,2…si件。而分组背包是不选,选第1个,或第2个或第3个…或第si个
//,都有si+1种决策方式,即使用三层循环即可解决。没有优化方式。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 10000;
int v[N];
int w[N];
int dp[N];
int n, m;
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int s;
		cin >> s;
		for (int k = 1; k <= s; k++)
		{
			cin >> v[k] >> w[k];
		}
		for (int j = m; j >=0; i--)
		{
			for (int k = 1; k <= s; k++)
			{
			if (j >= w[k])dp[j] = max(dp[j], dp[j - w[k]] + v[k]);
			}
		}
	}cout << dp[m] << endl;
	return 0;
}

dp悬线法

P1169 [ZJOI2007]棋盘制作
[参考代码]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2005;
int lft[N][N], rgt[N][N], upp[N][N], res[N][N];
int n, m, ans1 = -100, ans2 = -100;
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> res[i][j];
            lft[i][j] = rgt[i][j] = j;
            upp[i][j] = 1;
        }
    }
    //预处理左边界
    for (int i = 1; i <= n; i++) {
        for (int j = 2; j <= m; j++) {
         if (res[i][j] != res[i][j - 1])lft[i][j] = lft[i][j - 1];
        }
    }
    //预处理右边界
    for (int i = 1; i <= n; i++) {
        for (int j = m - 1; j >= 1; j--) {
         if (res[i][j] != res[i][j + 1])rgt[i][j] = rgt[i][j + 1];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i > 1 && res[i][j] != res[i - 1][j]) {
                upp[i][j] = upp[i - 1][j] + 1;
                lft[i][j] = max(lft[i][j], lft[i - 1][j]);
                rgt[i][j] = min(rgt[i][j], rgt[i - 1][j]);
            }
            int a = rgt[i][j] - lft[i][j] + 1;
            int b = min(a, upp[i][j]);
            ans1 = max(ans1, b * b);
            ans2 = max(ans2, a * upp[i][j]);
        }
    }cout << ans1 << endl;
    cout << ans2 << endl;
    return 0;
}







P4147 玉蟾宫
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
const int maxn = 1003;
int res[maxn][maxn], lft[maxn][maxn], rgt[maxn][maxn];
int upp[maxn][maxn];
int ans = -1000;
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	char c;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> c;
			if (c == 'F')res[i][j] = 1;
			else if (c == 'R')res[i][j] = 0;
			if (res[i][j])
			{
				lft[i][j] = rgt[i][j] = j;
				upp[i][j] = 1;
			}
		}
	}
	//预处理左边界
	for (int i = 1; i <= n; i++) {
		for (int j = 2; j <= m; j++) {
if (res[i][j] && res[i][j] == res[i][j - 1])lft[i][j] = lft[i][j - 1];
		}
	}
	//预处理右边界
	for (int i = 1; i <= n; i++) {
		for (int j = m - 1; j >= 1; j--) {
if (res[i][j] && res[i][j] == res[i][j + 1])rgt[i][j] = rgt[i][j + 1];
		}
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (res[i][j])
			{
				if (i > 1 && res[i][j] == res[i - 1][j])
				{
					upp[i][j] = upp[i - 1][j] + 1;
					lft[i][j] = max(lft[i][j], lft[i - 1][j]);
					rgt[i][j] = min(rgt[i][j], rgt[i - 1][j]);
				}


			}
			int a = rgt[i][j] - lft[i][j] + 1;
			ans = max(ans, a * upp[i][j]);
		}
	}
	cout << 3 * ans << endl;
	return 0;
}

多天完全背包

题目大意:小伟突然获得一种超能力,他知道未来 T 天 N 种纪念品每天的价格。
某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品
换回的金币数量。每天,小伟可以进行以下两种交易无限次:
1.任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;
2.卖出持有的任意一个纪念品,以当日价格换回金币。每天卖出纪念品换回的金币
可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,1直
持有纪念品也是可以的。
T 天之后,小伟的超能力消失。因此他一定会在第 T 天卖出所有纪念品换回金币。
小伟现在有 M 枚金币,他想要在超能力消失后拥有尽可能多的金币。
/*
【输入样例1】
6 1 100
50
20
25
20
25
50

【输入样例2】
3 3 100
10 20 15
15 17 13
15 25 16

【输出样例1】
305

【输出样例2】
217

*/

参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200;
int v[maxn][maxn];
const int maxm = 10005;
int dp[maxn][maxm];     
//dp[i][j]表示前i件物品,最多花费为j的最大利润,只需要进行t-1天,
//每天都跑一边完全背包,更新最大背包容积即可
int t, n, m;
int main()
{
	ios::sync_with_stdio(false);
	cin >> t >> n >> m;
	for (int i = 1; i <= t; i++)
	{
		for (int j = 1; j <= n; j++)cin >> v[i][j];
	}
	for (int i = 2; i <= t; i++)
	{
		memset(dp, 0, sizeof(dp));
		for (int j = 1; j <= n; j++)
		{
			for (int k = 0; k <= m; k++)
			{
				dp[j][k] = dp[j - 1][k];
if (k >= v[i - 1][j])
dp[j][k] = max(dp[j][k], dp[j][k - v[i - 1][j]] - v[i - 1][j] + v[i][j]);
			}
		}
		m += dp[n][m];
	}
	cout << m << endl;
	return 0;
}

到达型01背包

/*
题目大意:吉他手演唱n首歌曲,每次可以调节音量a【i】,
可以调大也可以调小,输入n,初始音量bl,最大音量ml,
音量值得范围是0到ml
输出演奏完最后一曲时所能到达得最大音量
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, bl, ml;
int a[55];
int dp[55][1003];//前i首歌能否到达音量j
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> bl >> ml;
	for (int i = 1; i <= n; i++)cin >> a[i];
	dp[0][bl] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = ml; j >= 0; j--)
		{
	if (j - a[i] >= 0)dp[i][j] = dp[i][j] || dp[i - 1][j - a[i]];
	if (j + a[i] <= ml)dp[i][j] = dp[i][j] || dp[i - 1][j + a[i]];
		}
	}
	for (int i = ml; i >= 1; i--)
	{
		if (dp[n][i])
		{
			cout << i << endl;
			return 0;
		}
	}
	cout << "-1" << endl;
	return 0;
}

LIS(一个序列的最长上升子序列)


法一:n^n做法:
	for(int i=1;i<=n;i++)
	{
		dp[i]=1;      //dp[i]表示以第i个元素为结尾的最长上升子序列
		for(int j=1;j<i;j++)
		{
			if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);
		}
	}


法二:nlogn做法:
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		dp[i]=0x3f3f3f3f;
//dp[i]表示长度为i的上升序列的末尾最小值,按照贪心思想,
//长度一定的上升序列的末尾数字越小,后面就可以加入更多的数字
	}
	dp[1]=a[1];
	int len=1; //记录当前dp数组存储的有效位数
	for(int i=2;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(a[i]>dp[len])dp[++len]=a[i]; 
		//如果刚好大于末尾,则暂时向后填充
		else
		{	
		//此处用lower_bound也可以,由dp[]数组的定义可知,
		//dp[]一定是单调递增的
			while(l<r)
			{
				mid=(l+r)/2;
				if(dp[mid]>a[i])r=mid;
				else l=mid+1;
			}
			dp[l]=min(a[i],dp[l]); 
			//更新末尾最小值
		}
	}cout<<len<<endl;
	注意,事实上,nlogn做法偷了个懒,
	并没有记录以每一个元素结尾的最长上升子序列的长度

LCS(两个序列的最长上升子序列)


用dp[i][j]数组来表示第一个串的前i位,第二个串的前j位的LCS的长度,
则状态转移方程:
若当前的A1[i]==A2[j],dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)
否则:dp[i][j]=max(dp[i-1][j],dp[i][j-1])

法一:n^2做法:
	int n,m;
	int dp[1003][1003];
	int a1[1003];
	int a2[1003];
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a1[i];
	for(int i=1;i<=m;i++)cin>>a2[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			if(a1[i]==a2[j])
			dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
		}
	cout<<dp[n][m]<<endl;


	法二:(洛谷大佬nb!)
	我们知道,最朴素的dp解LIS,LCS,复杂度都为O(n^2)
	我们还知道,用另一种dp解LIS,复杂度O(nlogn),
	这种方法本质上是一种状态定义法,使其可以二分
	LIS有O(nlogn)解法,LCS表示很嫉妒
	于是,他一想:LIS求给定数列A的最长上升子序列,本质上是将
	给定数列A.与一个表示大小关系的数列(此时为1到n递增排列)
	的公共子序列,换言之LIS就是隐形的LCS,那么LCS一定也可以
	变为LIS,只要定义出表示大小关系的数列就可以了
	,例如:求
	2 5 4 3 11 2 4 5 3的LCS
	只需要在数列1中重新定义大小关系,让2<5<4<3<1
	代码如下:
	int a[10003];
	int b[10003];
	int map[10004]; //新定义的顺序
	int dp[10004];
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		map[a[i]]=i;2<5<4<3<2
	}
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
		dp[i]=0x3f3f3f3f;
	}
	int len=0;
	dp[0]=0;
	for(int i=1;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(map[b[i]]>dp[len])dp[++len]=map[b[i]];
		else
		{
			while(l<r)
			{
				mid=(l+r)/2;
				if(dp[mid]>map[b[i]])r=mid;
				else l=mid+1;
			}dp[l]=min(dp[l],map[b[i]]);
		}
	}
	cout<<len<<endl;

LICS

//LICS 最长公共上升子序列
# include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int dp[3010][3010];
int a[3010];
int b[3010];
int n;
int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n; i++)cin >> b[i];
	/* //朴素版本
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			dp[i][j] = dp[i - 1][j];
			if (a[i] == b[j])
			{
				int maxx = 1;
				for (int k = 1; k < j; k++)
				{
			if (b[k] < b[j])maxx = max(maxx, dp[i - 1][k] + 1);
				}
				dp[i][j] = max(maxx, dp[i][j]);
			}
		}
	}*/
	//优化版本如下:
  // 优化第二层循环。因为a[i] == b[j], 
  //第三层循环的作用是求小于b[j]的方案数,
  //也即是求小于a[i]的方案数, 求完一个j后,剩下的j继续往后走变成j+1,
  //但第一层循环i保持不变。即if(b[j + 1] = a[i]), 
  //又会执行第三层循环,这时就会重复求解原来求过的数。
	for (int i = 1; i <= n; i++)
	{
		int maxx = 1;
		for (int j = 1; j <= n; j++)
		{
			dp[i][j] = dp[i - 1][j];
			if (b[j] < a[i])maxx = max(maxx, dp[i - 1][j] + 1);
			if (a[i] == b[j])dp[i][j] = max(dp[i][j], maxx);
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, dp[n][i]);
	}
	cout << ans << endl;
	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值