动态规划-DP 完整版

动态规划

学完了五大基础dp 做个简单总结

  • dp特征

动态规划问题 首要是找到做题的目的 是求最大/小值 还是其他;

其二 要确定问题的状态转移方程 这是关键;

第三 为dp数组找到边界、

最后 检查是否需要结合其他相关知识 如树 dfs等;

别忘了检查多测输入 数组变量置零等易错点。

  • 背包dp

以背包为抽象模型 主要依据某一条件限制求出另一值的最优解

  1. 01背包
    只有两个值 抽象化为体积和价值 要在体积一定的情况下找到价值的最大
    状态转移方程:

\[dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) \]

\(\qquad\,\,\)优化为一维:

\[dp[j]=max(dp[j],dp[j-w[i]]+v[i]) \]

code:

点击查看代码
int w[N],v[N],dp[N];
for(int i=1;i<=n;i++)
    for(int j=Vm;j>=w[i];j--)
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
  1. 完全背包
    在01背包基础上 物品没有了数量的限制 可以有无数个
    状态转移方程总体相同 更改了第二层体积循环的顺序
    code:
点击查看代码
for(int i=1;i<=n;i++)
    for(int j=w[i];j<=V;j++)
        dp[j]=max(dp[j],dp[j-w[i]]+v[i];
  1. 多重背包
    在01背包基础上 添加了部分可以取n次的物品
    此类题 思路是在输入时就将数据处理成01背包 然后直接套用01背包板子即可
    code:
点击查看代码
for(int i=1;i<=n;i++)
{
    int w,v,s;//体积 价值 数量
    int k=1;
    cin>>w>>v>>s;
    while(k<=s)
    {
        ww[++cnt]=k*w;
        vv[cnt]=k*v;
        s-=k;
        k*=2;
    }
    if(s)
    {
        ww[++cnt]=s*w;
        vv[cnt]=s*v;
    }
}
for(int i=1;i<=cnt;i++)
    for(int j=V;j>=ww[i];j--)
        dp[j]=max(dp[j],dp[j-ww[i]]+vv[i]);
  1. 混合背包
    即01 完全 多重背包的综合版
    方法是将多重背包转化为01背包求解即可
    code:

    (偷个懒 直接截图了Qwq

  2. 分组背包
    定体积 若干组物品 每组物品只能选一个
    在读入同时直接进行运算
    code:

点击查看代码
scanf("%d%d",&n,&V);//组数 体积
for(int i=1;i<=n;i++)
{
    scanf("%d",&t);//组中物品数
    for(int j=1;j<=t;j++)
        scanf("%d%d",&w[i],&v[i]);
    for(int j=c;j>=0;j--)
//这里要先遍历体积再遍历物品 确保每组物品只取一个
        for(int k=1;k<=t;k++)
        {
            if(j<w[k])continue;
            dp[j]=max(dp[j],dp[j-w[k]]+v[k]);
        }
}
特别的 每组最少取一个

题目在这里————code

  1. 二维费用背包
    即在01背包体积 价值两个条件下 增加一个抽象为质量的限制条件 使结果既不超过背包体积 也不超过能承受的最大质量
    嗯 直接枚举即可
    code:
点击查看代码
for(int i=1;i<=n;i++)
    for(int j=V;j>=w[i];j--)
        for(int k=M;k>=m[i];k--)
            dp[j][k]=max(dp[j][k],dp[j-w[i]][k-m[i]]+v[i]);
  • 线性dp

(比较抽象的一种dp 非常依靠玄学

该dp主要指一个状态包含多个维度 每个维度上都具有线性变化的阶段(晕晕

我们按例题进行分析

  1. LIS(最长上升序列)
    题目在这里
    问题很好解决 转移方程如下:

\[f[i]=max(f[j]+1) \]

\(\qquad\quad\,\,(0<=j<i,a[j]<a[i])\)
\(\qquad\,\,\)还有一点 就是这道题要求输出路径 我们还需要添加一个数组来递归输出
code:

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define opop cout<<"-------------------------"<<endl;
using namespace std;
inline int qr(){
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=10005;
const int maxx=0x3f3f3f;
int n,ans=0,mo=0;
int dh[N],zl[N],yy[N];
void read()
{
	int x;
	while(scanf("%d",&x)!=EOF)
		dh[++n]=x;
}
void DrRatio()
{
	fo(i,1,n)
	{
		zl[i]=1;
		fo(j,1,i-1)
			if(dh[i]>dh[j]&&zl[i]<zl[j]+1)
			{
				zl[i]=zl[j]+1;
				yy[i]=j;
			}
		if(ans<zl[i])
		{
			ans=zl[i];
			mo=i;
		}
	}
}
void opp(int x)
{
	if(x==0)return;
	opp(yy[x]);
	printf("%d ",dh[x]);
}
void op()
{
	printf("max=%d\n",ans);
	opp(mo);
}
int main()
{
	read();
	DrRatio();
	op();
	return Ratio;
}

  1. 拦截导弹
    题目在这里
    第一问很简单 最长不上升子序
    第二问需要用到Dilworth定理 不多阐述
    code:
点击查看代码
    int n,i,cnt=0,j,t;
    while(cin>>a[cnt]){
        cnt++;
    }
    int ans=0;
    for(i=0;i<cnt;i++)
    {
        dp[i]=1;
        for(j=0;j<cnt;j++)
        {
            if(a[i]<=a[j]&&i!=j)
                dp[i]=max(dp[i],dp[j]+1);
        }
        ans=max(ans,dp[i]);
    }
    cout<<ans<<endl;
    memset(dp,0,sizeof dp);
    ans=0;
    for(i=0;i<cnt;i++)
    {
        dp[i]=1;
        for(j=0;j<cnt;j++)
        {
            if(a[i]>a[j]&&i!=j)
                dp[i]=max(dp[i],dp[j]+1);
        }
        ans=max(ans,dp[i]);
    }
    cout<<ans<<endl;
  1. 麻烦的聚餐
    题目在这里
    这道题旨在于将给定的序列用最少的次数变成一个不上升或不下降序列
    常规方法可能会超时 所以需要加一些优化
    首先 读入同时倒序存储另一个数组
fo(i,1,n)
    dh[i]=qr,yy[n+1-i]=dh[i];

\(\qquad\,\,\)然后 将除dp[0]以外所用dp数组内所有值赋为极大值
\(\qquad\,\,\)按正序和倒序遍历两次 取结果中最小的哪一个
code:

点击查看代码
void DrRatio()
{
    fo(i,1,n)
        fo(j,1,3)
        {
            zl[i][j]=maxx;
            fu(k,j,1)
                zl[i][j]=min(zl[i][j],zl[i-1][k]);
            if(j!=dh[i])
                zl[i][j]++;
        }
    fo(i,1,3)
        ans=min(zl[n][i],ans);
    fo(i,1,n)
        fo(j,1,3)
        {
            zl[i][j]=maxx;
            fu(k,j,1)
                zl[i][j]=min(zl[i][j],zl[i-1][k]);
            if(j!=yy[i])
                zl[i][j]++;
        }
    fo(i,1,3)
        ans=min(zl[n][i],ans);
}
  1. 打鼹鼠
    题目在这里
    这道题是个典型dp 判断方式也很简单
    方法是定义一个以时间早晚排序的结构体 只要两者的距离差不大于时间差即可
    code:
点击查看代码
struct mole
{
	int time,x,y;
}dh[N];
bool cmp(mole A,mole B)
{
	return A.time<B.time;
}
bool pd(mole A,mole B)
{
	int tc=A.time-B.time,my=abs(B.y-A.y),mx=abs(B.x-A.x);
	return (mx+my)<=tc?true:false;
}
void read()
{
	n=qr,m=qr;
	fo(i,1,m)
		dh[i].time=qr,dh[i].x=qr,dh[i].y=qr;
	sort(dh+1,dh+m+1,cmp);
}
void DrRatio()
{
	fo(i,1,m)
	{
		zl[i]=1;
		fu(j,i-1,1)
			if(pd(dh[i],dh[j]))
				zl[i]=max(zl[i],zl[j]+1);
		ans=max(ans,zl[i]);
	}
}
  • 区间dp

这类题一般是将原序列化分为若干个区间 然后合并得出最优解 故也称合并动规

提到区间dp 就不得不联系到贪心 区间dp的题 一眼看上去都可以用贪心来做 但很容易举出反例 因此在觉得一道题是贪心的时候 先试着举出反例 若存在反例 则要按区间dp的思想来做

同上 这一类也用例题的形式回顾

  1. 石子合并问题
    题目在这里
    乍一眼看上去是贪心 但试着算一下 很容易发现不对劲
    首先 题目中石子摆成了一个环 因此我们采用扩大数组为原来二倍的方法存数据
    然后 用前缀和的形式存储到每一位石子的值
    状态转移方程为:

\[dp[i][j]=max(dp[i+1][j],dp[i][j-1])+sum[j]-sum[i-1] \]

\(\qquad\,\,\)其中\(j=len+i-1\)
\(\qquad\,\,\)边界为:dp[i][i]=0
\(\qquad\,\,\)最终取值为以每一位为首的环的最大值 表达式为:

\[ans=max(ans,dp[i][i+n-1]) \]

code:

点击查看代码
void read()
{
	n=qr;
	memset(rm,0,sizeof rm);
	memset(dh,-1,sizeof dh);
	fo(i,1,n)
	{
		yy[i]=qr;
		rm[i]=rm[i-1]+yy[i];
		dh[i][i]=0;
	}
}
void DrRatio()
{
	fo(i,1,n)
	{
		rm[i+n]=rm[i+n-1]+yy[i];
		dh[i+n][i+n]=0;
	}
	fo(len,2,n)
		fo(i,1,2*n-len)
		{
			int j=len+i-1;
			dh[i][j]=max(dh[i+1][j],dh[i][j-1])+rm[j]-rm[i-1];
		}
	fo(i,1,n)
		maax=max(maax,dh[i][i+n-1]);
}
  1. 整数划分
    题目在这里
    现在看来 这是一道明显的区间dp
    首先一个a数组 用来存储第i到j位的数
    然后dp数组 前i位分成j部分的最优解
    状态转移方程为:

\[dp[i][j]=max(dp[i][j],dp[k][j-1]*a[k+1][i]) \]

\(\qquad\,\,\)其中\(0<=k<i\)
\(\qquad\,\,\)边界为:

\[dp[i][0]=a[0][i] \]

\(\qquad\,\,\)对了 这一题还需要输出划分结果 因此在状态转移时要使用if判断 更改后随机更新y数组的值 最后递归输出
code:

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define opop cout<<"-------------------------"<<endl;
using namespace std;
inline int qr(){
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=105;
const int maxx=0x3f3f3f;
int T;
ll dh[N][N],yy[N][N],zl[N][N];
ll m,ne[N],len;
char s[N];
void read()
{
	memset(yy,-1,sizeof yy);
	memset(zl,-1,sizeof zl);
	ne[0]=0;
	scanf("%s%lld",s,&m);
	m--;
	len=strlen(s);
	fo(i,0,len-1)
	{
		dh[i][i]=s[i]-'0';
		fo(j,i+1,len-1)
			dh[i][j]=dh[i][j-1]*10+s[j]-'0';
	}
	fo(i,0,len-1)
	{
		zl[i][0]=dh[0][i];
		yy[i][0]=-1;
	}
}
void dg(ll x,ll y)
{
	if(yy[x][y]==-1)return;
	ne[++ne[0]]=yy[x][y];
	dg(yy[x][y],y-1);
}
void DrRatio()
{
	fo(i,0,len-1)
		fo(j,1,m)
			fo(k,0,i-1)
				if(zl[k][j-1]!=-1&&zl[k][j-1]*dh[k+1][i]>zl[i][j])
				{
					zl[i][j]=zl[k][j-1]*dh[k+1][i];
					yy[i][j]=k;
				}
}
void op()
{
	printf("%lld\n",zl[len-1][m]);
	dg(len-1,m);
	fo(i,0,len-1)
	{
		printf("%c",s[i]);
		if(ne[0]!=0&&i==ne[ne[0]])
		{
			printf(" ");
			ne[0]--;
		}
	}
	printf("\n");
}
int main()
{
	T=qr;
	while(T--)
	{
		read();
		DrRatio();
		op();
	}
	return Ratio;
}
  1. 最大乘积(印象最深的一集 写了一上午
    题目在这里
    看上去与上一题类似 但坑点就在于 需要用高精度
    整体思路大致相同 求出i到j位的数 然后相乘
    因为用到高精度 需要记录长度 因此我选择定义一个结构体 让所有运算在结构体内进行
    这次的详解放到代码注释里了~
    code:
点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define opop cout<<"-------------------------"<<endl;
using namespace std;
inline int qr(){
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=55;
const int maxx=0x3f3f3f;
int n,m;
char s[N];

struct rm
{
	int len;
	int a[105]={};
}dh[N][N],zl[N][N];
rm sw(rm A)//众所周知 高精度运算需要倒着进行 将A首尾倒置 
{
	int b[105];
	fo(i,0,A.len-1)
	{
		b[i]=A.a[A.len-i-1];
	}
	fo(i,0,A.len-1)
	{
		A.a[i]=b[i];
	}
	return A;
}
rm cheng(rm A,rm B)//高精乘 不必多说 
{
	rm C;
	A=sw(A);
	B=sw(B);
	C.len=A.len+B.len;
	fo(i,0,A.len-1)
		fo(j,0,B.len-1)
		{
			C.a[i+j]+=A.a[i]*B.a[j];
			C.a[i+j+1]+=C.a[i+j]/10;
			C.a[i+j]%=10;
		}
	while(C.a[C.len-1]==0&&C.len>1)C.len--;
	A=sw(A),B=sw(B),C=sw(C);
	return C;
}
rm Bma(rm A,rm B)//比较A与B的大小 为状态转移方程用 
{
	if(A.len>B.len)
		return A;
	else if(A.len<B.len)
		return B;
	else 
	{
		fo(i,0,A.len-1)
			if(A.a[i]>B.a[i])
				return A;
			else if(A.a[i]<B.a[i])
				return B;
		return A;
	}
}
void read()
{
	n=qr,m=qr;
	fo(i,1,n)
		cin>>s[i];
	fo(i,1,n)
	{
		dh[i][i].len=1;//初始化dh存i到i位的数 
		dh[i][i].a[0]=s[i]-'0';
		fo(j,i+1,n)//结论推导:存i到j位的数 
		{
			dh[i][j].len=dh[i][j-1].len+1;
			fo(k,0,dh[i][j-1].len-1)
				dh[i][j].a[k]=dh[i][j-1].a[k];
			dh[i][j].a[dh[i][j-1].len]=s[j]-'0';
		}
	}
}
void DrRatio()
{
	fo(i,1,n)
	{
		zl[i][0]=dh[1][i];//初始化/边界 
		fo(j,1,m)
			fo(k,j,i-1)
			{
				rm Rm=cheng(zl[k][j-1],dh[k+1][i]);
				zl[i][j]=Bma(zl[i][j],Rm);
			}
	} 
}
void op()
{
	fo(i,0,zl[n][m].len-1)
		cout<<zl[n][m].a[i];
}
int main()
{
	read();
	DrRatio();
	op();
	return Ratio;
}
  1. 凸多边形的三角剖分
    题目在这里
    这题难想的点在于状态转移方程
    首先 设一个多边形上的点为A
    dp含义为i到j组成三角形的最小值
    故点A后一点A+1必不能与A构成三角形
    所以\(dp[A][A+1]=0\)
    当然 自己和自己也不可能构成三角形
    然后 在这个多边形上任选一点B
    在AB间任选一点C
    可以得到:AC间剖成三角形的最小值与CB间剖成三角形的最小值的和 再加上三角形ABC的值 就是AB间剖分最小值
    状态转移方程为:

\[dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]) \]

\(\qquad\,\,\)其中\(j=len+i-1,j<=n;i<k<j\)
code:

点击查看代码
void read()
{
	memset(zl,0x3f,sizeof zl);
	n=qr;
	fo(i,1,n)
		scanf("%lld",&dh[i]);
	fo(i,1,n)
		zl[i][i]=zl[i][i+1]=0;
}
void DrRatio()
{
	fo(len,1,n)
		fo(i,1,n)
		{
			int j=min(len+i-1,n);
			fo(k,i+1,j-1)
				zl[i][j]=min(zl[i][j],zl[i][k]+zl[k][j]+dh[i]*dh[j]*dh[k]);
		}
}
  • 坐标dp

提到坐标 第一反应就是图

没错 坐标dp是需要存图的 但很简单 只用邻接矩阵即可

将数据存到图中 用遍历的方式找最优解 是坐标dp的精髓

  1. 传纸条
    题目在这里
    最经典的一道坐标dp题 题面也很清晰
    直接来看状态转移方程
    三维优化后为:

\[dp[k][i][j]=Max(dp[k-1][i][j],dp[k-1][i-1][j],dp[k-1][i][j-1],dp[k-1][i-1][j-1])+a[k-i][i]+a[k-j][j] \]

\(\qquad\,\,\)k为走的步数
\(\qquad\,\,\)其中\(k<m+n;1<=i<n;i<j<=n\)
code:

点击查看代码
void read()
{
	scanf("%d%d",&m,&n);
	fo(i,1,m)
		fo(j,1,n)
			scanf("%d",&dh[i][j]);
//	memset(zl,0,sizeof zl);
}
int Max(int a,int b,int c,int d)
{
	int e=a;
	if(b>e)e=b;
	if(c>e)e=c;
	if(d>e)e=d;
	return e;
}
void DrRatio()
{
	fo(k,3,m+n-1)
		fo(i,1,n-1)
			fo(j,i+1,n)
			{
				if(k<i+1||k<j+1)continue;
				zl[k][i][j]=Max(zl[k-1][i][j],zl[k-1][i-1][j],zl[k-1][i][j-1],zl[k-1][i-1][j-1])+dh[k-i][i]+dh[k-j][j];
			}
}
void op()
{
	printf("%d\n",zl[m+n-1][n-1][n]);
}
  1. 矩阵取数游戏
    题目在这里
    恶心的点在于 状态转移方程很难想
    好在 它可以将每一行分隔开单独看 并且用int128即可解决
    状态转移方程如下:

\[dp[j][k]=max(dp[j+1][k]*2+a[i][j],dp[j][k-1]*2+a[i][k]) \]

\(\qquad\,\,\)其中\(1<len<=m;1<=j<=m;k=j+len-1\)
code:

点击查看代码
int n,m,dh[N][N];
__int128 zl[N][N],ans=0;
void print(__int128 a)
{
	if(a>9)
		print(a/10);
	putchar(a%10+'0');
}
void read()
{
	n=qr,m=qr;
	fo(i,1,n)
		fo(j,1,m)
			dh[i][j]=qr;
}
void DrRatio()
{
	fo(i,1,n)
	{
		fo(j,1,m)
			zl[j][j]=dh[i][j];
		fo(l,2,m)
			for(int j=1,k=j+l-1;k<=m;j++,k++)
				zl[j][k]=max(zl[j+1][k]*2+dh[i][j],zl[j][k-1]*2+dh[i][k]);
		ans+=zl[1][m]*2;
	}	
}
void op()
{
	print(ans);
}
  1. 免费馅饼
    题目在这里
    这道题要用结构体存图
    此外 还有两个关键点:
    1>人站在最下面一格 所以下落高度要-1
    2>不是整数时刻落下的接不到
    直接上代码吧
    code:
点击查看代码
#include <bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
using namespace std;
inline int qr()
{
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=1005;
const int maxx=0x7f7f7f7f;
int w,h,cnt,maax=-maxx,ans=0;
int a,b,c,d;
int yy[6]={0,-2,-1,0,1,2};
int zl[N][N];
struct rm
{
	int tt,x,v;
}dh[10*N];
void read()
{
	w=qr,h=qr;h--;
	while(scanf("%d%d%d%d",&a,&b,&c,&d)!=EOF)
	{
		if(h%c!=0)continue;
		dh[++cnt].x=b;
		dh[cnt].v=d;
		dh[cnt].tt=a+ceil(h/c);
	}
}
void DrRatio()
{
	fo(i,1,cnt)
	{
		zl[dh[i].x][dh[i].tt]+=dh[i].v;
		maax=max(maax,dh[i].tt);
	}
	fu(j,maax-1,0)
		fo(i,1,w)
		{
			ans=0;
            fo(k,1,5)
				if(i+yy[k]>0&&i+yy[k]<=w&&zl[i+yy[k]][j+1])
					ans=max(ans,zl[i+yy[k]][j+1]);
			zl[i][j]+=ans;
		}
}
void op()
{
	printf("%d\n",zl[w/2+1][0]);
}
int main()
{
	read();
	DrRatio();
	op();
	return Ratio;
}
  1. 三角蛋糕
    题目在这里
    这道题有个坑 如果只按题目给定的0和1来存 就会出现虽然数字构成了三角形 但是方向是反的 从而得到了错误答案
    因此 要反着推一遍
    状态转移方程为:

\[dp[i][j]=min(dp[i±1][j],min(dp[i±1][j±1],dp[i±1][j±2]))+1 \]

\(\qquad\,\,\)注意 i与j的运算符号相反
code:

点击查看代码
#include <bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
using namespace std;
inline int qr()
{
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=505;
const int maxx=0x7f7f7f7f;
char s[N][N];
int n,ans=0;
int dh[N][N],zl[N][N];
void read()
{
	n=qr;
	fo(i,1,n)
		scanf("%s",s[i]+1);//以1为起点 
}
void DrRatio()
{
	fo(i,1,2*n-1)//第一行特殊处理 
		if(s[1][i]!='1')
			zl[1][i]=1,ans=1;
	fo(i,2,n)
		fu(j,2*(n-i)+1,1)//倒 
		{
			if(s[i][j]!='1')
				zl[i][j]=min(zl[i-1][j],min(zl[i-1][j+1],zl[i-1][j+2]))+1;//上一行左中右都不被损坏 
			if(j&1)
				ans=max(ans,zl[i][j]);
		}
	if(s[n][1]=='1')dh[n][1]=1;
	fu(i,n-1,1)//反着推一遍 
		fu(j,2*(n-i)+1,1)
		{
			if(s[i][j]!='1')
				dh[i][j]=min(dh[i+1][j],min(dh[i+1][j-1],dh[i+1][j-2]))+1;
			if(!(j&1))
				ans=max(ans,dh[i][j]);
		}
}
void op()
{
	printf("%d\n",ans*ans);
}
int main()
{
	read();
	DrRatio();
	op();
	return Ratio;
}
  • 树形dp

算法如其名 树形dp是建立在树上的dp

而树不存在回路 因此dfs是解决树形dp的主要方法

  1. 没有上司的舞会
    最简单 最经典的树规题
    \(dp[i][0]\)表示i没有参会
    \(dp[i][1]\)表示i参会
    状态转移方程为:
    \(dp[x][0]+=max(dp[son[x][i]][0],dp[son[x][i]][1])\)
    \(dp[x][1]+=dp[son[x][i]][0]\)
    code:
点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define foo(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
#define opop cout<<"-------------------------"<<endl;
using namespace std;
inline int qr(){
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;}
#define qr qr()
typedef long long ll;
const int Ratio=0;
const int N=10005;
const int maxx=0x3f3f3f;
vector<int>so[N];
int n,ro;
int zl[N][2],fx[N],joy[N];
void read()
{
	n=qr;
	fo(i,1,n)	
		joy[i]=qr;
	fo(i,1,n)
	{
		int x=qr,y=qr;
		if(i==n)break;
		fx[x]=1;
		so[y].push_back(x);
	}
}
void rRatio(int x)
{
	zl[x][0]=0;
	zl[x][1]=joy[x];
	for(int i=0;i<so[x].size();i++)
	{
		int a=so[x][i];
		rRatio(a);
		zl[x][0]+=max(zl[a][0],zl[a][1]);
		zl[x][1]+=zl[a][0];
	}
}
void DrRatio()
{
	fo(i,1,n)
		if(!fx[i])
		{
			ro=i;
			break;
		}
	rRatio(ro);
}
void op()
{
	printf("%d\n",max(zl[ro][0],zl[ro][1]));
}
int main()
{
	read();
	DrRatio();
	op();
	return Ratio;
}
  1. 三色二叉树
    写了这一题的题解
    在这里看

大体就这么多了 再想到会补充的

制作不易 求个推荐(✪ω✪)

posted @ 2024-02-17 17:59  DrRatio  阅读(160)  评论(18编辑  收藏  举报