基础+进阶DP

本文深入探讨了动态规划(DP)在解决背包问题、线性DP、最长上升序列模型等方面的应用,包括01背包、完全背包、多重背包等经典问题。通过实例解析,展示了如何运用DP解决实际问题,如数字三角形的最大路径和、最低通行费等。此外,还介绍了状态压缩DP、树形DP和记忆化搜索等高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包问题

01背包问题

有 N件物品和一个容量是 V的背包。每件物品只能使用一次
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N]; 
int f[N][N];//从前i个物品中选,总体积不超过j的最大价值 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	  {
	  	f[i][j]=f[i-1][j];
	     if(j>=v[i])  //放得下 
	     f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); 
		}  
		cout<<f[n][m]<<endl;
		return 0;
}

优化从大到小循环


#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N];   
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
       cin>>v[i]>>w[i];
         //从前i-1中选,体积不超过j的最大价值
       
    for(int i=1;i<=n;i++)
        for(int j=m;j>=v[i];j--)   //从大到小
           f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[m]; 

完全背包问题

有 N种物品和一个容量是 V的背包,每种物品都有无限件可用
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N]; 
int f[N][N];//从前i个物品中选,总体积不超过j的最大价值 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	
	for(int i=1;i<=n;i++)
	  for(int j=0;j<=m;j++)
	   for(int k=0;k*v[i]<=j;k++)
	     f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]); 
		 
		cout<<f[n][m]<<endl;
		return 0;
}

在这里插入图片描述

优化 从小到大循环

#include<iostream>
using namespace std;

const int N=1010; 
int n,m;
int v[N],w[N],f[N];
 
 int main()
 {
    cin>>n>>m;
    for(int i=1;i<=n;i++)  cin>>v[i]>>w[i];
    
     for(int i=1;i<=n;i++)
       for(int j=v[i];j<=m;j++)   //从小到大
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    
    cout<<f[m];   
}

多重背包问题

有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N]; 
int f[N][N];//从前i个物品中选,总体积不超过j的最大价值 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
	
	for(int i=1;i<=n;i++)
	  for(int j=0;j<=m;j++)
	   for(int k=0;k*v[i]<=j&&k<=s[i];k++)
	     f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]); 
		 
		cout<<f[n][m]<<endl;
		return 0;
}

多重背包问题II

分组背包问题

有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 vij,价值是 wij,其中 i是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N组数据:
每组数据第一行有一个整数 Si,表示第 i个物品组的物品数量;每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int w[N][N],v[N][N],s[N]; 
int f[N];//从前i个物品中选,总体积不超过j的最大价值 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
	{
		cin>>s[i];
		for(int j=0;j<s[i];j++)
		  cin>>v[i][j]>>w[i][j]; //第i个物品组的第j个物品
	}
	
	for(int i=1;i<=n;i++)
	  for(int j=m;j>=0;j--)     //从大到小
	   for(int k=0;k<s[i];k++)
	      if(v[i][k]<=j)//第i个物品组的第k个物品
	     f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); 
		 
		cout<<f[m]<<endl;
		return 0;
}

线性DP

数字三角形模型

数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

         7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N= 510,INF=1e9;
int f[N][N];  //所有从起点走到(i,j)点的路径 
int a[N][N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) 
	 for(int j=1;j<=i;j++)
	    cin>>a[i][j];
	    
	for(int i=0;i<=n;i++) 
	 for(int j=0;j<=i+1;j++)
	   f[i][j]=-INF;  //处理边界
	   
	   f[1][1]=a[1][1];
	for(int i=2;i<=n;i++) 
	 for(int j=1;j<=i;j++) 
	   f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
	   
	int res=-INF;

	for(int i=1;i<=n;i++)    res=max(res,f[n][i]);
	cout<<res<<endl;
	return 0;   
}

摘花生

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010;
int n,m;
int a[N][N],f[N][N]; 
int main()
{
	int t;
	cin>>t;

  while(t--)
   {
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	  cin>>a[i][j];
	  
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
		f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];
	
	cout<<f[n][m]<<endl;
   }
}

最低通行费

请添加图片描述
输入
5
1 4 6 8 10
2 5 7 15 17
6 8 9 18 20
10 11 12 19 21
20 23 25 29 33
输出
109

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010,INF=1e8;
int n,m;
int a[N][N],f[N][N]; 
int main()
{


	cin>>n;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	  cin>>a[i][j];
	 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(i==1&&j==1)  f[i][j]=a[i][j];//特判左上角
	  else
	  {
	     f[i][j]=INF;
	     if(i>1)  f[i][j]=min(f[i][j],f[i-1][j]+a[i][j]);//不在第一行,才可以从上面过来
	     if(j>1)  f[i][j]=min(f[i][j],f[i][j-1]+a[i][j]);
    	}
	}
	cout<<f[n][n]<<endl;
   
}

方格取数

请添加图片描述

最长上升序列模型

最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010;
int f[N];  //以第i个数结尾的上升子序列集合 
int a[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i]; 

    for(int i=1;i<=n;i++)
    {
    	f[i]=1;   //只有a[i]一个数 
    	for(int j=1;j<i;j++)
    	if(a[j]<a[i])
    	  f[i]=max(f[i],f[j]+1);
	}
	int res=0;
	for(int i=1;i<=n;i++)   res=max(res,f[i]);
	
	cout<<res<<endl;
	
}

输出方案数

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010;
int f[N];  //以第i个数结尾的上升子序列集合 
int a[N],g[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i]; 

    for(int i=1;i<=n;i++)
    {
    	f[i]=1;   //只有a[i]一个数 
    	g[i]=0;
    	for(int j=1;j<i;j++)
    	if(a[j]<a[i])
    	  if(f[i]<f[j]+1)
		  {
		  	  f[i]=f[j]+1;
		  	  g[i]=j;
		   } 
    	  
	}
	int k=1;
	for(int i=1;i<=n;i++)
	  if(f[k]<f[i])   k=i;
	printf("%d\n",f[k]);
	
	for(int i=0,len=f[k];i<len;i++)
	{
		printf("%d ",a[k]);
		k=g[k];
	}
	
}

怪盗基德的滑翔伞

在这里插入图片描述

输入
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出
6
6
9

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=110;
int n;
int a[N],f[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)  cin>>a[i];
		
		//正向求解LIS问题
		int res=0;
		for(int i=1;i<=n;i++)
		{
			f[i]=1;
			for(int j=1;j<i;j++)
				if(a[j]<a[i])  f[i]=max(f[i],f[j]+1);
			res=max(res,f[i]);
		 } 
		 //反向求解
		 for(int j=n;j>=1;j--)
		 {
		 	f[j]=1;
		 	for(int i=n;i>j;i--)
		 	  if(a[j]>a[i])
		 	     f[j]=max(f[j],f[i]+1);
		 	res=max(res,f[j]);
		  }   
		  cout<<res<<endl;
	}
	
 } 

登山

在这里插入图片描述

 //注意:减一 
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n;
int a[N],f[N],g[N]; 
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i];
	//正向
	for(int i=1;i<=n;i++)
	{
	    f[i]=1;
	  for(int j=1;j<i;j++)
	     if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
	 }
	 //反向
	 for(int i=n;i>0;i--)
	{
	    g[i]=1;
	  for(int j=n;j>i;j--)
	     if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);
	 }
	 //枚举中间值
	 int res=0;
	 for(int i=1;i<=n;i++)
	 res=max(res,f[i]+g[i]-1); //中间加了两次;
	 
	 cout<<res<<endl; 
}

合唱队形

请添加图片描述
8
186 186 150 200 160 130 197 220
4

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n;
int a[N],f[N],g[N]; 
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>a[i];
	
	for(int i=1;i<=n;i++)
	{
	    f[i]=1;
	  for(int j=1;j<i;j++)
	     if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
	 }
	 
	 for(int i=n;i>0;i--)
	{
	    g[i]=1;
	  for(int j=n;j>i;j--)
	     if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);
	 }
	 int res=0;
	 for(int i=1;i<=n;i++)
	 res=max(res,f[i]+g[i]-1); //中间加了两次;
	 
	 cout<<n-res<<endl; 
	 
}

友好城市

请添加图片描述
请添加图片描述
输入
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出
4

#include<iostream>
#include<cstring>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int,int>PII;
const int N=5010;
int n;
PII q[N];
int f[N]; 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	  cin>>q[i].x>>q[i].y;
	sort(q,q+n);    //fist第一关键字
	
	int res=0;
	for(int i=0;i<n;i++)
	{
		f[i]=1;
		for(int j=0;j<i;j++)
		  if(q[i].y>q[j].y)  f[i]=max(f[i],f[j]+1);
		 
		 res=max(res,f[i]);
	}
	cout<<res;
}

最大上升子序列和

输入
7
1 7 3 5 9 4 8
输出
18

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010;
int n;
int a[N],f[N]; 
int main()
{
	cin>>n;
	int res=0;
	for(int i=0;i<n;i++)  cin>>a[i];
	for(int i=0;i<n;i++)
	{
		f[i]=a[i];
		for(int j=0;j<i;j++)
		   if(a[i]>a[j])
		      f[i]=max(f[i],f[j]+a[i]);
		res=max(res,f[i]);
	}
	cout<<res<<endl;
}

最长上升子序列II——贪心

拦截导弹

请添加图片描述

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1010;
int n;
int q[N],f[N],g[N];
int main()
{
	while(cin>>q[n])   n++;
	
	int res=0;
	for(int i=0;i<n;i++)
	{
		f[i]=1;
		for(int j=0;j<i;j++)
		 if(q[i]>q[j])  f[i]=max(f[i],f[j]+1);
		res=max(res,f[i]);
	}
	cout<<res<<endl;
	
	int cnt=0;  //子序列个数 
	for(int i=0;i<n;i++)
	{
		int k=0;  //从前往后找的序列 
		while(k<cnt&&g[k]<q[i])  k++;
		g[k]=q[i];
		if(k>=cnt)  cnt++;  //开一个新的序列 
	}
	cout<<cnt<<endl;
	return 0;
}

导弹防御系统+DFS

*最长公共子序列

在这里插入图片描述
输入
4 5
acbd
abedc
输出
3

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];//所有在第一个序列的前i个字母中出现,并且第二个序列的前j个字母中出现的子序列的最大值 
int main()
{
	// 
	cin>>n>>m;
	scanf("%s%s",a+1,b+1);
	for(int i=1;i<=n;i++)
	   for(int j=1;j<=m;j++)
	   {
	   	 f[i][j]=max(f[i-1][j],f[i][j-1]);
	   	 if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
	   }
	cout<<f[n][m]<<endl;
	return 0;
}

最长公共上升子序列

输入
2213
2123
输出
2(分析:23)

f[i][j]:所有在a[1...i]和b[1...j]中出现过,并且以b[j]结尾的最长公共上升子序列

//TLE
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=3010;
int f[N][N],a[N],b[N];
int n;

int main()
{
	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++)
	  {
	  	f[i][j]=f[i-1][j];
	  	if(a[i]==b[j])
	  	{
	  	   int maxv=1;   //注意
	  	   for(int k=1;k<j;k++)
	  	     if(b[k]<b[j])
	  	       maxv=max(maxv,f[i-1][k]+1);
		   f[i][j]=max(f[i][j],maxv);
		}
		
	  }
	  int res=0;
	  for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	      res=max(res,f[i][j]);
	     
	cout<<res<<endl;
	return 0;
 } 

优化

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=3010;
int f[N][N],a[N],b[N];
int n;

int main()
{
	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++)
	 {
	   int maxv=1;
	   for(int j=1;j<=n;j++)
	  {
	  	f[i][j]=f[i-1][j];
	  	if(a[i]==b[j])  f[i][j]=max(f[i][j],maxv);
	  	if(b[j]<a[i])   maxv=max(maxv,f[i-1][j]+1);
	  
	  }
}
	  int res=0;
	  for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	      res=max(res,f[i][j]);
	      
	      
	cout<<res<<endl;
	return 0;
 } 

*编辑距离

给定n个 长度不超过10的字符串以及m次询问,每次询问给出一个字符串和一个操作次数上限
对于每次询问,请你求出给定的n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
输入
3 2
abc
acd
bcd
ab 1
abcd 2
输出格式
1
3

区间DP

石子合并

设有 N堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N
堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1、2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;
如果第二步是先合并 2、3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N个数,表示每堆石子的质量(均不超过 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=310;
int n;
int s[N],a[N];
int f[N][N];//将第i堆到第j堆合并最小代价 
int main()
{
	cin>>n;
   for(int i=1;i<=n;i++)   cin>>a[i];
   for(int i=1;i<=n;i++)  s[i]=s[i-1]+a[i];
   
   for(int len=2;len<=n;len++)  //枚举区间长度 
   {
   	  for(int i=1;i+len-1<=n;i++)  //枚举区间左端点
   	  {
   	  	int l=i,r=i+len-1;
   	  	f[l][r]=1e8;
   	  	for(int k=l;k<r;k++)
			 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]) ;
	 }
   }
	printf("%d\n",f[1][n]);
}

环形石子合并

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=410,INF=0x3f3f3f3f;
int n;
int s[N],w[N];
int f[N][N],g[N][N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
		w[i+n]=w[i];
	}
	for(int i=1;i<=n*2;i++)   s[i]=s[i-1]+w[i];
	memset(f,0x3f,sizeof f);
	memset(g,-0x3f,sizeof g);
	for(int len=1;len<=n;len++)
	for(int l=1;l+len+1<=n*2;l++)
	{
		int r=l+len-1;
		if(len==1) f[l][r]=g[l][r]=0;
		else
		{
			for(int k=l;k<r;k++)
			  {
			  	f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
			    g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
			  }
		}
	}
	int minv=INF,maxv=-INF;
	for(int i=1;i<=n;i++)
	{
		minv=min(minv,f[i][i+n-1]);
		maxv=max(maxv,g[i][i+n-1]);
	}
	cout<<minv<<endl<<maxv<<endl;
}

能量项链

在这里插入图片描述
4
2 3 5 10
输出
710

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=210;
int n;
int w[N];
int f[N][N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
		w[i+n]=w[i];
	}

	for(int len=3;len<=n+1;len++)
	for(int l=1;l+len-1<=n*2;l++)
	{
		int r=l+len-1;
			for(int k=l+1;k<r;k++)
			  	f[l][r]=max(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
	}
    int res=0;
	for(int i=1;i<=n;i++)
      res=max(res,f[i][i+n]);
	cout<<res<<endl;
}

凸多边形的划分

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=60,M=35,INF=1e9;//最多30位 
int n;
int w[N];
int f[N][N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
	
	}

	for(int len=3;len<=n;len++)
	for(int l=1;l+len-1<=n;l++)
	{
		int r=l+len-1;
		f[l][r]=INF;
		for(int k=l+1;k<r;k++)
			f[l][r]=min(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);	
	}
   
	cout<<f[1][n]<<endl;
}

高精度

输入
39
558 602 725 854 612 867 554 857 433 633 655 394 447 307 901 66 973 282 393 487 801 822 538 489 578 402 935 846 81 347 97 658 30 378 842 967 788 150 880
输出
368205270

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL; 
const int N=60,M=35,INF=1e9;//最多30位 
int n;
int w[N];
LL f[N][N][M];
void add(LL a[],LL b[])
{
	static LL c[M];//辅助数组
	memset(c,0,sizeof c);
	for(int i=0,t=0;i<M;i++)
	{
		t+=a[i]+b[i];
		c[i]=t%10;
		t/=10;
	 } 
	 memcpy(a,c,sizeof c);
}
void mul(LL a[],LL b)
{
	static LL c[M];
	memset(c,0,sizeof c);
	LL t=0;
	for(int i=0;i<M;i++)
	{
		t+=a[i]*b;
		c[i]=t%10;
		t/=10;
	}
	memcpy(a,c,sizeof c);
}
int cmp(LL a[],LL b[])
{
	for(int i=M-1;i>=0;i--)  //最高位在最后一位 
	  if(a[i]>b[i])  return 1;
	  else if(a[i]<b[i])  return -1;
	  return 0;
}
void print(LL a[])
{
	int k=M-1;
	while(k&&!a[k])k--; //过滤前导0;
	while(k>=0)  cout<<a[k--];
	cout<<endl;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
	
	}
   LL tmp[M];
	for(int len=3;len<=n;len++)
	for(int l=1;l+len-1<=n;l++)
	{
		int r=l+len-1;
	 	f[l][r][M-1]=1;   //z正无穷 100000...
		for(int k=l+1;k<r;k++)
		{
			memset(tmp,0,sizeof tmp);
			tmp[0]=w[l];
			mul(tmp,w[k]);
			mul(tmp,w[r]);
			add(tmp,f[l][k]);
			add(tmp,f[k][r]);
			if(cmp(f[l][r],tmp)>0) 
			  memcpy(f[l][r], tmp,sizeof tmp);
		}	 		  
		
	}
   
	print(f[1][n]);
}

加分二叉树

在这里插入图片描述
输出
145
3 1 2 4 5

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL; 
const int N=30;//最多30位 
int n;
int w[N];
int  f[N][N],g[N][N];
void dfs(int l,int r)
{
	if(l>r)  return ;
	int root=g[l][r];
	cout<<root<<' ';
	dfs(l,root-1);
	dfs(root+1,r);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
	
	}
  
	for(int len=1;len<=n;len++)
	for(int l=1;l+len-1<=n;l++)
	{
		int r=l+len-1;
	 	if(len==1)
		 {
		   f[l][r]=w[l];  //叶节点
		   g[l][r]=l;
    	}
	    	else
	 	{
	 		for(int k=l;k<=r;k++)
	 		{
	 			int left=k==l?1:f[l][k-1];  //是左端点:左子树为空 
	 			int right=k==r?1:f[k+1][r];
	 			int score=left*right+w[k];
	 			if(f[l][r]<score)
	 			{
	 				f[l][r]=score;
	 				g[l][r]=k;
				 }
			 }
		 }	
	}
   
cout<<f[1][n]<<endl;
dfs(1,n);
}

棋盘分割——二维

在这里插入图片描述
输入
3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3
输出
1.633

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL; 
const int N=15,M=9;//最多30位 
const double INF=1e9;
int n,m=8;
int s[M][M];
double  f[M][M][M][M][N];
double x;
double get(int x1,int y1,int x2,int y2)
{
	double sum=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]-x;
    return sum*sum/n;
 } 
double dp(int x1,int y1,int x2,int y2,int k)
{
	double &v=f[x1][y1][x2][y2][k];
	if(v>=0)  return v;//被算过 
	if(k==1)  return v=get(x1,y1,x2,y2);  //只有一部分,不能在切 
    
    v=INF;
    for(int i=x1;i<x2;i++)
    {
    	v=min(v,dp(x1,y1,i,y2,k-1)+get(i+1,y1,x2,y2));  //选上面 
    	v=min(v,dp(i+1,y1,x2,y2,k-1)+get(x1,y1,i,y2));
	}
	//枚举列
	for(int i=y1;i<y2;i++)
	{
		v=min(v,dp(x1,y1,x2,i,k-1)+get(x1,i+1,x2,y2));
		v=min(v,dp(x1,i+1,x2,y2,k-1)+get(x1,y1,x2,i));
	 } 
	 return v;
}
int main()
{
	cin>>n;
	for(int i=1;i<=m;i++)
	  for(int j=1;j<=m;j++)
	     {
	     	cin>>s[i][j];
	     	s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		 }
	memset(f,-1,sizeof f);
	x=(double)s[m][m]/n;
	printf("%.3lf",sqrt(dp(1,1,8,8,n)));
	return 0;
}

计数类DP

整数划分

数位统计DP

计数问题

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。
例如,a=1024,b=1032,则 a 和 b 之间共有 9
个数如下:
1024 1025 1026 1027 1028 1029 1030 1031 1032
其中 0 出现 10
次,1 出现 10
次,2 出现 7
次,3 出现 3
次等等…
输入格式
输入包含多组测试数据。
每组测试数据占一行,包含两个整数 a 和 b。
当读入一行为 0 0 时,表示输入终止,且该行不作处理。
输出格式
每组数据输出一个结果,每个结果占一行。
每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。
数据范围
0<a,b<100000000
输入样例:
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出样例:
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

状态压缩DP

蒙德里安的梦想

在这里插入图片描述
1 2
1
1 3
0
1 4
1
2 2
2
2 4
5
2 3
3
2 11
144
4 11
51205
0 0

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=12,M=1<<N;
long long f[N][M];
bool st[M];
int main()
{
	int n,m;
	while(cin>>n>>m,n||m)
	{
		memset(f ,0,sizeof f);
		
		for(int i=0;i<1<<n;i++)
		{
			st[i]=true;
			int cnt=0;  //连续0的个数,不能存在连续奇数个0 
			for(int j=0;j<n;j++)
			  if(i>>j&1) //上一段截止 
			  {
			  	if(cnt&1) st[i]=false;
			  	cnt=0;
			  }
			  else cnt++;
			  if(cnt&1) st[i]=false;  //判断最后一个是不是合法 
		}
		//DP过程 
		f[0][0]=1;
		for(int i=1;i<=m;i++)  //枚举列 
		  for(int j=0;j<1<<n;j++)  //枚举所有状态 
		    for(int k=0;k<1<<n;k++)  //枚举i-1列的所有状态 
		      if((j&k)==0&&st[j|k])
		        f[i][j]+=f[i-1][k];
		  //枚举到      
		cout<<f[m][0]<<endl;	
	}
 } 

骑士

在这里插入图片描述
输入
3 2
输出
16

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=12,M=1<<10,k=110;
int n,m;
vector<int> state;   //所有合法状态
vector<int>head[M];  //可以转移到的其他状态
 
int id[M],cnt[M]; 
int f[N][k][M];
bool check(int state)  //连续两个1 
{
	for(int i=0;i<n;i++)
	  if((state>>i&1)&&(state>>i+1&1))
	    return false;
	return true;
}
int count(int state)
{
	int res=0;
	for(int i=0;i<n;i++)   res+=state>>i&1;
	return res;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<1<<n;i++)
	
		if(check(i))
		{
				state.push_back(i);
				id[i]=state.size()-1;//  合法状态对应的下标 
				cnt[i]=count(i);   //i一的个数 
		}
	
	
	for(int i=0;i<state.size();i++)
	 for(int j=0;j<state.size();j++)
	 {
	 	int a=state[i],b=state[j];
	 	if((a&b)==0&&check(a|b))
	 	  head[i].push_back(j);   //只要b合法,就添加到a可以转移到的状态集合里去 
	 }
	 f[0][0][0]=1;
	 for(int i=1;i<=n+1;i++)   //多一行 
	  for(int j=0;j<=m;j++)  //m:国王数量 
	  {
	  	for(int a=0;a<state.size();a++)//枚举所有状态 
	  	   for(int b:head[a])  //枚举所有a能到的状态 (c++11) 
	        {
	        	int c=cnt[state[a]];  //a里1的个数 
	        	if(j>=c)
	        		f[i][j][a]+=f[i-1][j-c][b];
				
			}
	  }
	cout<<f[n+1][m][0]<<endl;
}

玉米田

输入
2 3
1 1 1
0 1 0
输出
9

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=14,M=1<<12;
int n,m;
int g[N];
vector<int>state;
vector<int>head[M];
int f[N][M];
bool check(int state)
{
	for(int i=0;i<m;i++)
	 if((state>>i&1)&&(state>>i+1&1))
	   return false;
	return true;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  for(int j=0;j<m;j++)
	  {
	  	int t;
	  	cin>>t;
	  	g[i]+=!t<<j;  //0则置一 
	  }
	  for(int i=0;i<1<<m;i++)
	  	if(check(i))
	  	  state.push_back(i);
	   
	   for(int i=0;i<state.size();i++)
	     for(int j=0;j<state.size();j++)
	     {
	     	int a=state[i],b=state[j];
	     	if((a&b)==0)  head[i].push_back(j);//i可以转移到j 
		 }
		 
		 f[0][0]=1;
		 for(int i=1;i<=n+1;i++)
		   for(int a=0;a<state.size();a++)
		   for(int b:head[a])
		   {
		   	  if(g[i]&state[a])  continue;
		   	  f[i][a]+=f[i-1][b];
			} 
			cout<<f[n+1][0]<<endl;
}

炮兵阵地

输入
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出
6

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=11,M=1<<10;
int n,m;
int g[110],cnt[M];
vector<int>state;
int f[2][M][M];  //滚动数组 
bool check(int state)
{
	for(int i=0;i<m;i++)
	 if((state>>i&1)&&((state>>i+1&1)|(state>>i+2&1)))
	   return false;
	return true;
}
int count(int state)
{
	int res=0;
	for(int i=0;i<m;i++)   res+=state>>i&1;
	return res;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  for(int j=0;j<m;j++)
	  {
	  	char t;
	  	cin>>t;
	  	if(t=='H')  //山地 
	  	g[i]+=1<<j;  //0则置一 
	  }
	  for(int i=0;i<1<<m;i++)
	  	if(check(i))
	  	  {
			state.push_back(i);
			cnt[i]=count(i);
		}
	   
	   for(int i=1;i<=n+2;i++)
	     for(int j=0;j<state.size();j++)  //i-1行 
	      for(int k=0;k<state.size();k++)  //i行 
	        for(int u=0;u<state.size();u++)
	       {
	     	int a=state[j],b=state[k],c=state[u];
	     	if((a&b)|(b&c)|(a&c))  continue;
	     	if((g[i-1]&a)|g[i]&b)  continue;
	        f[i&1][j][k]=max(f[i&1][j][k],f[i-1&1][u][j]+cnt[b]);
		 }
		 
		
			cout<<f[n+2&1][0][0]<<endl;
}

愤怒的小鸟

输入
3
1 0
4.38 5.62
2 0
1.85 3.63
2.94 8.89
2 0
2.27 0.62
6.40 7.87
输出
1
2
2

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define x first
#define y second
using namespace std;
const int N=18,M=1<<18;
const double eps=1e-6;
typedef pair<double,double> PDD;
int  n,m;
PDD q[N];
int path[N][N],f[M];
int cmp(double x,double y)
{
	if(fabs(x-y)<eps)  return 0;
	if(x<y)  return -1;
	return 1;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		for(int i=0;i<n;i++)  cin>>q[i].x>>q[i].y;
		memset(path,0,sizeof path);
		for(int i=0;i<n;i++)
		{
			path[i][i]=1<<i;
			for(int j=0;j<n;j++)
			{
				double x1=q[i].x,y1=q[i].y;
				double x2=q[j].x,y2=q[j].y;
				if(!cmp(x1,x2))  continue;
				double a=(y1/x1-y2/x2)/(x1-x2);
				double b=(y1/x1)-a*x1;
				if(a>0)  continue;
				int state=0;
				for(int k=0;k<n;k++ )
				{
					double x=q[k].x,y=q[k].y;
					if(!cmp(a*x*x+b*x,y))  state+=1<<k;
				}
				path[i][j]=state;
			}
		}
		memset(f,0x3f,sizeof f);
		f[0]=0;
		for(int i=0;i+1<1<<n;i++)
		{
			int x=0;
			for(int j=0;j<n;j++)
				if(!(i>>j&1))
				{
					x=j;  //没有被覆盖的列 
					break;
				}
			for(int j=0;j<n;j++)
				f[i|path[x][j]]=min(f[i|path[x][j]],f[i]+1);			 
			
		}
		cout<<f[(1<<n)-1]<<endl;
	}
	
}

积木画

最短Ha路径

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20,M=1<<N;
int n;
int w[N][N];
int f[M][N];

int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	  for(int j=0;j<n;j++)
	    cin>>w[i][j];
	memset(f,0x3f,sizeof f);
	f[1][0]=0;
	for(int i=0;i<1<<n;i++)
	   for(int j=0;j<n;j++)
	     if(i>>j&1)
	       for(int k=0;k<n;k++)
	         if((i-(1<<j))>>k&1)
	           f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
	cout<<f[(1<<n)-1][n-1]<<endl;
}

树形DP

没有上司的舞会

Ural 大学有 N 名职员,编号为 1∼N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入格式
第一行一个整数 N。
接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。
接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。(注意一下,后一个数是前一个数的父节点,不要搞反)。
输出格式
输出最大的快乐指数。
数据范围
1≤N≤6000,
−128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5

树的最长路径

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int N=10010,M=N*2;

int n;
int h[N],e[M],w[M],ne[M],idx;
int ans;
void add(int a,int b,int c)
{
	e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int dfs(int u,int father)
{
	int dist=0;// 表示从当前点往下走的最大长度
     int d1=0,d2=0;
     

	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father)  continue;
		int d=dfs(j,u)+w[i];
		dist=max(dist,d);
		if(d>=d1)
		d2=d1,d1=d;
	    else if(d>d2)  d2=d;
	 } 
	 ans=max(ans,d1+d2);
	 return dist;
 } 
int main()
{
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++)
	{
		int a,b,c;  cin>>a>>b>>c;
		add(a,b,c),add(b,a,c);
	}
	dfs(1,-1);  //任取取1号点为根节点 
	cout<<ans;
}

树的中心

在这里插入图片描述
输入
5
2 1 1
3 2 1
4 3 1
5 1 1
输出
2

记忆化搜索

滑雪

状态机

大盗阿福

输入
2
3
1 8 2
4
10 7 6 14
输出
8
24

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1e5+10,INF=0x3f3f3f3f;
int n;
int w[N],f[N][2];

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)  scanf("%d",&w[i]);
		f[0][0]=0,f[0][1]=-INF;  //入口指向0;
		for(int i=1;i<=n;i++)
		{
			f[i][0]=max(f[i-1][0],f[i-1][1]);
			f[i][1]=f[i-1][0]+w[i];
		 } 
		 
		cout<<max(f[n][0],f[n][1]);
	}
	
 } 

股票买卖IV

不能同时参与多笔交易
输入
3 2
2 4 1
输出
2
输入
6 2
3 2 6 5 0 3
输出
7

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,M=110;
int n,m;
int f[N][M][2],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)  cin>>w[i];
    memset(f,-0x3f,sizeof f);
   for(int i=0;i<=n;i++)   f[i][0][0]=0;
   
   for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++)
	 {
	 	f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+w[i]);
	 	f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-w[i]);
	  } 
   
   int res=0;
   for(int i=0;i<=m;i++)  res=max(res,f[n][i][0]);//最后手中无货 
	cout<<res;
	
}

股票买卖VI

不能同时参与多笔交易
卖出后冷冻一天,不能参加交易
输入
5
1 2 3 0 2
输出
3

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,INF=0x3f3f3f3f;
int n;
int f[N][3],w[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)  cin>>w[i];
	f[0][0]=f[0][1]=-INF;
	f[0][2]=0;
  
   for(int i=1;i<=n;i++)
	 {
	 	f[i][0]=max(f[i-1][0],f[i-1][2]-w[i]);
	 	f[i][1]=f[i-1][0]+w[i];
	 	f[i][2]=max(f[i-1][1],f[i-1][2]);
	  } 
 cout<<max(f[n][1],f[n][2]);
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值