自学DP记录(一):线性DP

刚刚开始学习DP的,请DP大人对我好一点。。(显然没有)
(由于是自学,没有什么定义或者什么简单结论,所以 <我不对我所说的话负法律责任>

血泪警告:状态以及转移方程一定要自己好好推一遍,而且是要自己出来!!!

学这个的时候就是总看着题解推的,所以现在就基础非常不扎实,以至于快考试了,我还是找不到写DP的感觉,一遇到就爆零,让我难受了好长时间,我决定从头开始好好再刷一遍!!这次要自己推,而且是要自己理解,自己敲代码!!不能看题解!

上题:

线性动态规划

第一题:打鼹鼠
初学DP打完数字三角形后的第一题,,
不是很难,主要看注释。。。
代码:

#include<bits/stdc++.h>
#define sea 11000
using namespace std;
int n,f[sea],m,ans=0;
struct watch{int t,x,y;}a[sea];
bool compare(watch x,watch y){return x.t<y.t;}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
		cin>>a[i].t>>a[i].x>>a[i].y;
	sort(a+1,a+n+1,compare);//按时间排序
	for(int i=1;i<=m;i++)
	{
		f[i]=1;//赋初值
		for(int j=1;j<i;j++)
		if(abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y)<=a[i].t-a[j].t)//关键步骤,如果距离(自己模拟一下就等于跳格子的数量)小于或等于时间间隔,就加入跳格子的次数
		f[i]=max(f[j]+1,f[i]);//加入跳格子的次数f[i]
		ans=max(f[i],ans);//每次对f[i]取max,就是答案
	}	
	cout<<ans<<endl;
	return 0;
} 

最长上升子序列

先发个板子:

//LIS
//朴素算法 
#include<bits/stdc++.h>
#define LL long long 
using namespace std;
const int sea=6000;
int n,a[sea];
int f[sea];//表示到第i个数时的最长上升子序列长度 
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main()
{
	n=read();
	int ans=0; 
	for(int i=1;i<=n;i++) a[i]=read(),f[i]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		if(a[i]>=a[j])
		f[i]=max(f[i],f[j]+1);
	}
	for(int i=1;i<=n;i++) ans=max(ans,f[i]);
	cout<<ans;
	return 0;
}


//lower_bound二分优化(还是觉得lower_bound比较好写,这个相当手写的于二分查找)
#include<bits/stdc++.h>
using namespace std;
const int sea=600000;
int f[sea];//表示到第i个数时的最长上升子序列
int n,a[sea];
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	f[1]=a[1];
	int len=1;
	for(int i=2;i<=n;i++)
	{
		if(a[i]>f[len]) f[++len]=a[i];
		else
		{
			int j=lower_bound(f+1,f+len+1,a[i])-f;
			//lower_bound是返回在f[]数组中从1开始f+len+1之前的数中最大的<=a[i]的数的下标是什么 
			f[j]=a[i];
		}
	}
	//最后的答案就自认存在了len中
//	for(int i=1;i<=n;i++) cout<<f[i]<<' ';
//	f[i]也就是最长上升子序列中的一种 
	printf("%d",len);
	return 0;
}

例题:导弹拦截

//O(n^2)
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
} 
const int sea=210;
int n,g[sea],f[sea],a[sea],ans1,ans2;//这个状态存的是到达i之前的最大不上升子序列的长度
int main()
{
	n=read(); over(i,1,n) a[i]=read(),f[i]=g[i]=1; 
	over(i,1,n) over(j,1,i-1) if(a[i]<=a[j]) f[i]=max(f[i],f[j]+1);
	over(i,1,n) over(j,1,i-1) if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);
	over(i,1,n) ans1=max(f[i],ans1),ans2=max(g[i],ans2); printf("%d\n%d\n",ans1,ans2); 
	return 0;
}



//O(nlogn)
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
} 
const int sea=210;
int n,len=0,a[sea],f[sea];//这个状态存的是逆序的a[]数组
void ef1(int x)
{
	int l=1,r=len;
    while(l<r){int mid=(l+r)>>1;if(x>f[mid]) r=mid; else l=mid+1;}
    f[l]=x; 
}
void ef2(int x)
{
	int l=1,r=len; 
	while(l<r){int mid=(l+r)>>1;if(x<=f[mid]) r=mid; else l=mid+1;}
	f[l]=x; 
}
int main()
{
    scanf("%d",&n); over(i,1,n)scanf("%d",&a[i]),f[i]=1;
	over(i,1,n) if(len==0||a[i]<=f[len]) f[++len]=a[i];else ef1(a[i]); printf("%d\n",len);len=0;
    over(i,1,n) if(a[i]>f[len]) f[++len]=a[i]; else ef2(a[i]); printf("%d\n",len);
    return 0;
}

第二题 尼克船长
题解:
两种状态:
1.当他可以闲着的时候:f[i]=f[i+1]+1;
2.当他有空的时候:f[i]=max(f[i],f[i+a[num].s]);
代码:

#include<bits/stdc++.h>
#define sea 10001
#define ll long long //不用想就要开long long 
using namespace std;
ll n,m,t,s,sum[sea],f[sea];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
struct see{ll t,s;}a[sea];
bool compare(see a,see b){return a.t>b.t;}
int main()
{
	n=read(); m=read();
	for(ll i=1;i<=m;i++)
	{
		a[i].t=read(); a[i].s=read(); 
		sum[a[i].t]++;//记下有工作的时间
	}
	sort(a+1,a+m+1,compare);//倒着排
	int num=1;
	for(ll i=n;i>=1;i--)//倒着搜
	{
		if(sum[i]==0) f[i]=f[i+1]+1;//没有任务空闲的时候
		else
		for(ll j=1;j<=sum[i];j++)//有任务的时候(存在多重任务的时候)
		{
			f[i]=max(f[i],f[i+a[num].s]);//状态转移方程,具体解释看上方
			num++;//就是个计数器。。。
		}
	}	
	cout<<f[1]<<endl;
	return 0;
}

最长公共子序列

第三题:编译距离
一道很好的板子题
还是两个状态:
1.如果两个字母一样,f[i][j]=f[i-1][f-1];
2.如果两个字母不一样的话三种情况取最小值
动态转移方程:
f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+1;
代码:

#include<bits/stdc++.h>
#define sea 2010
using namespace std;
int n,m,f[sea][sea];
char a[sea],b[sea];
int main()
{
	scanf("%s %s",a,b);
	m=strlen(a); n=strlen(b); 
	for(int i=1;i<=m;i++) f[i][0]=i;
	for(int j=1;j<=n;j++) f[0][j]=j;
	for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	{
		if(a[i-1]==b[j-1])//第一状态(由于a,b是字符串从0开始的所以减一)
		{
			f[i][j]=f[i-1][j-1];
			continue;
		}
		f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+1;//第二状态(分别为:更改,插入,删除)
	}	
	printf("%d",f[m][n]);
	return 0;
}

合并型DP:

第四题:石子合并
一道杠了很长时间的板子题,向大神请教了好长时间,所以打算好好讲一下(并不是说我之前就没有好好讲!!!)
首先对一个环中得两块石头进行合并的时候,分三个循环走:
1.整个环
2.环上的起始点
3.状态转移点
最重要的状态转移方程:
x[i][j]=min(x[i][j],x[i][k]+x[k+1][j]+sum[j]-sum[i-1]);//最小值
比较x[i][k]和x[k+1][j]之间是否对答案有贡献,然后加上它们的前缀和(这里的sum[j]-sum[i-1]如果不懂的话,看下图)
在这里插入图片描述
(字太丑了,对不住了,就凑乎看吧)
最大值也一样:
y[i][j]=max(y[i][j],y[i][k]+y[k+1][j]+sum[j]-sum[i-1]);//最大值
代码:

#include<bits/stdc++.h>
#define sea 5000
#define ocean 1100000
using namespace std;
int n,x[sea][sea],y[sea][sea],a[ocean],sum[ocean];
inline int read()
{
	int s=0,w=1; char ch=getchar(); 
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		a[i+n]=a[i];//处理环 
	}	
	for(int i=1;i<=n*2;i++)
	x[i][i]=0,y[i][i]=0,sum[i]=sum[i-1]+a[i];//xy的处理及前缀和 
	for(int len=2;len<=n;len++)//循环 环 
	{
		for(int i=1;i<=n*2-len+1;i++)//循环 起始点 
		{
			int j=len+i-1;
			x[i][j]=0x3f;y[i][j]=-1;//赋初值 
			for(int k=i;k<=j-1;k++)//循环 转移点  
			{
				x[i][j]=min(x[i][j],x[i][k]+x[k+1][j]+sum[j]-sum[i-1]);//最小值 
				y[i][j]=max(y[i][j],y[i][k]+y[k+1][j]+sum[j]-sum[i-1]);//最大值 
			} 
		}	
	}		
	int ans1=0x3f,ans2=0;
	for(int i=1;i<=n;i++)
	{
		ans1=min(ans1,x[i][i+n-1]);//最小答案 
		ans2=max(ans2,y[i][i+n-1]);//最大答案 
	}	
	printf("%d %d",ans1,ans2);
	return 0;
}

当然这是个赤裸裸的区间dp(n3)算法,现在就是展现新算法的时候了!!!(好草的开场白)

石子合并平行四边形优化

解法明天明天明天准时,,,
代码:

#include<iostream>
#include<cstring>
#define sea 2015
using namespace std;
int n,a[sea],dp[sea][sea],sum[sea],s[sea][sea];
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch<='9'&&ch>='0') s=s*10+ch-'0',ch=getchar();
	return s*w;
}	
int main()
{
	n=read();
	sum[0]=0; 
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
		a[i]=read(); 
		sum[i]=sum[i-1]+a[i];
		s[i][i]=i,dp[i][i]=0;
	}
    for(int i=1;i<n;i++) 
	{
		sum[i+n]=sum[i+n-1]+a[i];
		s[i+n][i+n]=i+n,dp[i+n][i+n]=0;
	}
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i<=2*n-1;i++)
		{
			int j=i+len-1;
			if(j>2*n-1) break; 
			for(int k=s[i][j-1];k<=s[i+1][j];k++)
			if(dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
			dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1],s[i][j]=k;//最小值,由于最大值不具有单调性所以用四边形不等式无法进行
		}
	}
	int ans=0x3fffffff;
	for(int i=1;i<=n;i++)  
	ans=min(ans,dp[i][n+i-1]);
	printf("%d\n",ans);
	return 0;
}

枚举型动态规划

第五题: 滑雪课
一个非常有意思的暴力题,,,
题解:
最重要的事放第一位:能力值最大是100,最多有100节滑雪课(这也是枚举型动态规划的前提条件(至少要跑的过啊))
先说一下最最最最重要预处理
一个for循环直接处理出imp[i]就是能力值最小的时去滑雪的最短时间(便于到dp中的先滑雪的优化情况)
然后就是次重要的c[i][j]数组:
记录c[这节课开始的时间][第几节课]=这个时间有几节课,这样记录之后就可以推出:
l[c[i][k]]:就是从i时间点开始的第k节课的时间长度
a[c[i][k]]:就是从i时间点开始的第k节课之后变为的能力值
然后就是最简单的 DP了,先全部处理成-1,便于之后的跳过,然后就分为三种状态去优化即可
(f[i][j]表示时刻i能力值为j时能够完成的最多滑雪次数 )
1.休息:f[i+1][j]=max(f[i+1][j],f[i][j]);
2.滑雪(一定要先滑雪在上课,可以推一下样例例如下面推的样例图):f[i+imp[j]][j]=max(f[i+imp[j]][j],f[i][j]+1);
3.上课:
先上个循环找到每节课的能力值和时间长度然后再优化:
f[i+tmp][pow]=max(f[i+tmp][pow],f[i][j]);
最后答案就是1到100之间的最大滑雪时间即可。

在这里插入图片描述
代码:

#include<bits/stdc++.h>
#define sea 11000
using namespace std;
struct watch{int power,time;}snow[10001];
int t,s,n,a[101],m[101],l[101],f[10001][101],c[10001][101],imp[101];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar(); }
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
//能力值最大是100,最多有100节滑雪课 
void yuchuli()
{
	for(int i=1;i<=100;i++)
	{
		imp[i]=11000;//先赋为最大值 
		for(int j=1;j<=n;j++)
		if(snow[j].power<=i)//选出最小的能力 
		imp[i]=min(snow[j].time,imp[i]);//选出所需时间最小的的雪坡 
	}
}	
int main()
{
	t=read(); s=read(); n=read();
	for(int i=1;i<=s;i++)
	{
		m[i]=read(); l[i]=read(); a[i]=read();
		c[m[i]][0]++; c[m[i]][c[m[i]][0]]=i;
	}
	for(int i=1;i<=n;i++) snow[i].power=read(),snow[i].time=read();
	yuchuli();
	for(int i=0;i<=t;i++)
	for(int j=1;j<=100;j++)
	f[i][j]=-1;
	f[0][1]=0;
	for(int i=0;i<=t;i++)
	{
		for(int j=1;j<=100;j++)
		{
			if(f[i][j]==-1) continue;
			f[i+1][j]=max(f[i+1][j],f[i][j]);//休息 
			f[i+imp[j]][j]=max(f[i][j]+1,f[i+imp[j]][j]);//自己滑雪
			for(int k=1;k<=c[i][0];k++)//上课 
			{
				int tm=l[c[i][k]];//这门课所持续的时间 
				int pw=a[c[i][k]];//上完这门课能够到达的能力值 
				f[i+tm][pw]=max(f[i+tm][pw],f[i][j]);
			}		
		}
	}
	int ans=0;
	for(int i=1;i<=100;i++)  ans=max(f[t][i],ans);
	printf("%d",ans);
	return 0;
}	

规则类动态规划

第六题:传纸条
就像是左边的大神所说的,用图论就能写,dfs就能过的简题,但是为了好好的学我的DP,就决定好好去写一下DP:
不算太难打的一道题,但是快读困了我好长时间,但是换成scanf就过了,(是我快读出错了吗??|墙|ョ゚ェ゚;))
还是状态转移:当传纸条的时候,有四种情况:
在这里插入图片描述
f[i][j][k]可以表示为[横纵坐标的和][此点的左边点的纵坐标][此点的右边点的纵坐标]
1.可以不传,还在A: f[i-1][j][k]
2.可以传到B: f[i-1][j][k-1]
3.可以传到C: f[i-1][j-1][k]
4.可以传到D: f[i-1][j-1][k-1]
代码:

#include<bits/stdc++.h>
#define sea 101
using namespace std;
int n,m,a[sea][sea],f[sea][sea][sea];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	scanf("%d",&a[i][j]);
	f[2][1][1]=0;
	for(int i=3;i<n+m;i++)
	for(int j=1;j<=n;j++)
	for(int k=j+1;k<=n;k++)
	{
		if(m<=i-j&&i+j<=1&&m<=i-k&&i+k<=1) continue;//不出界,就像是搜索一样
		f[i][j][k]=max(f[i-1][j][k],max(f[i-1][j-1][k-1],max(f[i-1][j-1][k],f[i-1][j][k-1])));
		f[i][j][k]+=a[j][i-j]+a[k][i-k];
	}
	int ans=f[n+m-1][n-1][n]+a[n][m];
	printf("%d",ans);
	return 0;
}

环形DP

第七题: 多边形游戏
经典的区间DP的变形,最重要的一点就是你要知道负负得正即可。
直接上状态转移方程式:
加法:
1、f[i][j][1]=max(f[i][j][1],f[i][k][1]+f[k+1][j][1]);
2、f[i][j][0]=min(f[i][j][0],f[i][k][0]+f[k+1][j][0]);
乘法:
1、正正
2、正负
3、负正
4、负负
(直接取(12334)最大值和最小值即可)
代码:

#include<iostream>
#define sea 300
#define ocean 32768
#define rain -32768//特别好理解的最大小值
using namespace std;
int n,a[sea],f[sea][sea][2];
char cot[sea];
int main()
{
	cin>>n;int t;
	for(int i=1;i<=n;i++)
	{
		t=i-1;
		if(t==0) t=n;//处理运算符的下表 
		//处理运算符的环 
		cin>>cot[t];
		cot[t+n]=cot[t];
		//处理点的环 
		cin>>f[i][i][1];
		f[i][i][0]=f[i][i][1];
		f[i+n][i+n][1]=f[i][i][1];
		f[i+n][i+n][0]=f[i][i][1];
	}
	for(int len=1;len<n;len++)//经典的区间DP做法 
	for(int i=1;i<=n+n;i++)
	{
		int j=i+len;
		f[i][j][1]=rain;
		f[i][j][0]=ocean;
		for(int k=i;k<j;k++)
		{
			if(cot[k]=='t')//加法 
			{
				f[i][j][1]=max(f[i][j][1],f[i][k][1]+f[k+1][j][1]);
				f[i][j][0]=min(f[i][j][0],f[i][k][0]+f[k+1][j][0]);
			}
			else//乘法 
			{
				int a,b,c,d;
				a=f[i][k][0]*f[k+1][j][0];
				b=f[i][k][1]*f[k+1][j][0];
				c=f[i][k][1]*f[k+1][j][1];
				d=f[i][k][0]*f[k+1][j][1];
				f[i][j][1]=max(f[i][j][1],max(max(a,b),max(c,d)));
				f[i][j][0]=min(f[i][j][0],min(min(a,b),min(c,d)));
			}
		}
	}	
	int ans=rain;
	for(int i=1;i<=n;i++)
	ans=max(ans,f[i][i+n-1][1]);//找出最大值 
	cout<<ans<<endl;
	for(int i=1;i<=n;i++)
	if(f[i][n+i-1][1]==ans)//找出断开点 
	cout<<i<<' ';
	return 0;
}

资源分配类DP

第八题: 花店橱窗布置
先%%%%%%一下石神tqltqltql
在石神的细心讲解下,本菜鸡终于参透了这种类型的DP了,,,(高兴地飞了起来)
这道题就是典型的资源分配DP,就是把几种花分到几个花瓶中,最重要的是要把顺序或者是每种花如何分配的输出来,这就不是很好想了。。
还是先写转移方程:
f[i][j]表示:[前i种花朵][第j个花瓶]=最大美学值
f[i][j]=max(f[i][j-1],f[i-1][j-1]+a[i][j]);
但是要记录每次的f[i][j]给怎么办呢???
再次%%%%石神,再开个新数组:g[i][j].x表示i,g[i][j].y表示j即可;
然后就是最重要的递归输出:
其实每次++ss即可;
最后记得倒序输出哦,,

#include<bits/stdc++.h>
#define sea 520
using namespace std;
int n,m,a[sea][sea],f[sea][sea],ans[sea],ss=0;;
struct solu
{
	int x,y;
}g[sea][sea];
void pri(int i,int j)
{
	if(!j) return ;
	int x=g[i][j].x,y=g[i][j].y;
	if(x<i) ans[++ss]=j;
	pri(x,y);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	cin>>a[i][j];
	for(int i=0;i<=n;i++)
	for(int j=0;j<=m;j++)
	f[i][j]=-0x3f;
	for(int i=0;i<=m;i++)
	f[0][i]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=m;j++)
		{
			if(f[i][j-1]>=f[i-1][j-1]+a[i][j])
			f[i][j]=f[i][j-1],g[i][j].x=i,g[i][j].y=j-1;
			else
			f[i][j]=f[i-1][j-1]+a[i][j],g[i][j].x=i-1,g[i][j].y=j-1;
		}
	}
	printf("%d\n",f[n][m]);
	pri(n,m);
	for(int i=ss;i>=1;i--)
	printf("%d ",ans[i]);
	return 0;
}
双倍经验:

机器分配

#include<bits/stdc++.h>
#define sea 120
using namespace std;
int  n,m,a[sea][sea],f[sea][sea],xx;
int print(int i,int j)
{
	if(i==0) return 0;
	for(int k=0;k<=j;k++)
	{
		if(f[i-1][k]+a[i][j-k]==xx)
		{
			xx=f[i-1][k];
			print(i-1,k);
			cout<<i<<' '<<j-k<<endl;
			break;
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	cin>>a[i][j];
	memset(f,0,sizeof(f));
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		xx=0;//用这个记录即可(相当于上面的g[i][j])
		for(int k=0;k<=j;k++)
		{
			xx=max(xx,f[i-1][k]+a[i][j-k]);
			f[i][j]=xx;
		}
	}
	cout<<f[n][m]<<endl;
	print(n,m);
	return 0;
	
}

区间DP

第九题:加分二叉树
非常有意思的题,巧妙的将树和DP联系在一起的DP题,没有很难但是很练习基本功。
先复习一下树的前序,中序,后序
在这里插入图片描述
前序: 先根再左右子树——1 3 2
中序: 先左再中最后右——3 1 2
后序: 先左右子树再根——3 2 1
此题共有两问,可以分开处理
第一问:DP求最大值:
典型的区间DP,直接上状态转移方程了:
if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k])
f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k],root[i][j]=k;
记录下来root[i][j]方便查找前序;
第二问:求前序:
print直接递归就好了,代码很容易懂的;

#include<bits/stdc++.h>
#define sea 500
using namespace std;
int n,f[sea][sea],root[sea][sea];
void print(int l,int r)
{
   if(l>r) return ;
   cout<<root[l][r]<<' ';
   if(l==r) return ;
   print(l,root[l][r]-1);
   print(root[l][r]+1,r);
}
int main()
{
   cin>>n;
   for(int i=1;i<=n;i++) 
   {
   	cin>>f[i][i];
   	f[i][i-1]=1;
   	root[i][i]=i;
   }
   for(int len=1;len<n;len++)
   {
   	for(int i=1;i+len<=n;i++)
   	{
   		int j=len+i;
   		f[i][j]=f[i+1][j]+f[i][i];
   		root[i][j]=i;
   		for(int k=i+1;k<j;k++)
   		{
   			if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k])
   			{
   				f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
   				root[i][j]=k;
   			}
   		}
   	}
   }
   cout<<f[1][n]<<endl;
   print(1,n);
   return 0;
}

学了一个星期多的零乱线性DP,整体觉得还算都通了一遍,下面就该学最最最最最最经典的背包了,(觉得背包会好难)。。。\( T﹏T )/

难才要去学,不难就没有学的意义了。 —— blng

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值