首页 > 其他分享 >区间dp 和 树型dp

区间dp 和 树型dp

时间:2023-04-29 11:55:32浏览次数:28  
标签:int siz 枚举 区间 树型 where dp

区间dp

递推方程是以区间的形式给出 一般套路 :枚举区间长度 区间端点 区间分界点
然后就是想怎么去对这个区间进行一定的操作 从最原始的地方开始一步步推导方程!

for(i=1;i<n;i++)//区间长度为1
{
	for(j=1;j<=n-i;j++) //区间开头
	{
	for(k=j;k<j+i;k++)  //分界点
	{
	f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]+s[j+i]-s[j-1]);
	g[j][j+i]=max(g[j][j+i],g[j][k]+g[k+1][j+i]+s[j+i]-s[j-1]);
	}
	}
	}

如果区间是一个环形那就开2倍空间:去看从哪一个点断开的去枚举端点!!不妨为一个好思路

#include <bits/stdc++.h>
using namespace std;
int n;
int a[501],f[501][501],s[501];
int main()
{
	cin>>n;
	int i,j,k;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
		a[i+n]=a[i];
	}
	int t=n*2;
	for(i=1;i<=t;i++)
	{
		s[i]=s[i-1]+a[i]; 
	}
	memset(f,127,sizeof(f));
	for(i=1;i<=t;i++)  //长度为0 的区间
	f[i][i]=0;
	for(i=1;i<t;i++)   //因为转移方程  是由两个小区间转移到大区间
					   //故要按照区间大小进行排序先更新每一个小区间保证
					   //后面的区间更新时用到的值是有效的
	{
		for(j=1;j<=t-i;j++)//区间的开头 
		{
			for(k=j;k<j+i;k++)  //区间分界线  
			f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]+s[j+i]-s[j-1]);
		}
	}
	int ans=1<<30;
	
	for(i=1;i<=n;i++)
	{ 
		ans=min(ans,f[i][i+n-1]);
	}
	cout<<ans;
}

这个地方就是去看那一段长度为n 的子区间最小 应为是断点从什么地方开始断开之后形成的
相当于把区间分成了两份往断口合并!
主要就是看如何对这一个区间的东西进行一定的操作!!!来达到目的的实现!!
当然也有变种也就是给定一个原始区间对该原始区间进行dp 添加或者减少数字!!

#include <bits/stdc++.h>  //区间dp的好题目
using namespace std;
typedef int64_t i64;
int mod=19650827;
int a[1005];
int f[1005][1005][2];
int n;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		f[i][i][0]=1;
	}
	for(int k=1;k<n;k++)
	{
		for(int i=1,j=i+k;j<=n;j++,i++)
		{
			if(a[i]<a[i+1]) f[i][j][0]+=f[i+1][j][0];
			if(a[i]<a[j]) f[i][j][0]+=f[i+1][j][1];
			if(a[j]>a[i]) f[i][j][1]+=f[i][j-1][0];
			if(a[j]>a[j-1]) f[i][j][1]+=f[i][j-1][1];
			f[i][j][0]%=mod;
			f[i][j][1]%=mod;
		}
	}
	cout<<(f[1][n][0]+f[1][n][1])%mod;
}

这个就是一个原始区间然后不断去枚举添加数字!!看可以从那一个最开始的区间开始不断推导出新的东西!长度为 i+1的区间由长度为i的区间推导若开头是得到可以是j+1j+i也可以是从jj+i-1推导得到!!这个地方很好!!就是递推由小问题不断推出大问题,去写递推方程!!像递归的逆过程 递归就是大化小直至化为可以解决的东西

#include <bits/stdc++.h>
using namespace std;
int n,m;
inline __int128 read(){
   __int128 x = 0, f = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){
   	if(ch == '-')
   		f = -1;
   	ch = getchar();
   }
   while(ch >= '0' && ch <= '9'){
   	x = x * 10 + ch - '0';
   	ch = getchar();
   }
   return x * f;
}
inline void print(__int128 x){
   if(x < 0){
   	putchar('-');
   	x = -x;
   }
   if(x > 9)
   	print(x / 10);
   putchar(x % 10 + '0');
}
#define i128 __int128
i128 a[85];
i128 f[85][85]; //区间枚举!!
i128 ans;		//答案记录!!
i128 base[85];
void solve()
{
   for(int i=1;i<=m;i++)
   {
   	for(int j=1;j<=m;j++)
   	{
   		f[i][j]=0;
   	}
   }
   for(int i=1;i<=m;i++)
   {
   	a[i]=read();
   }
   //	for(int i=1;i<=m;i++)
   //	{
   //		//f[i][i]=2*a[i];//这是每次取走一个元素!!
   //		print(a[i]);
   //		cout<<endl;
   //	}
   //print(base[5]);
   //cout<<endl;
   for(int i=1;i<=m;i++)
   {
   	for(int j=m;j>=i;j--)
   	{
   		f[i][j]=max(f[i][j],f[i-1][j]+base[m-j+i-1]*a[i-1]);
   		f[i][j]=max(f[i][j],f[i][j+1]+base[m-j+i-1]*a[j+1]);
   	}
   }
   i128 mx=0;
   for(int i=1;i<=m;i++)
   {
   	mx=max(mx,f[i][i]+base[m]*a[i]);
   }
   //	print(mx);
   //	cout<<endl;
   ans+=mx;
}
int main()
{
   cin>>n>>m;
   base[0]=1;
   for(int i=1;i<=m;i++)
   {
   	base[i]=base[i-1]*2;
   }
   for(int i=1;i<=n;i++)
   {
   	solve();
   }
   print(ans);
}

这道题用了 int 128 题目本身是不难的这也算是多了个东西!主要就是这道题记录的原因就是给一道int128使用的方法看看,以后可以参照这个模板去写,这道题倒是没什么!!像合唱队模型的变种一样!!
主要就是看是如何对区间进行操作 是一步步化还是可以将区间划分后合并的那种!!
针对不同问题写出递推方程即可

高贵的树形dp

树形dp有两种一种是根可变 一种是根不可变
其中也有一定的思维方式 根可变就是计算当一个节点为根和不为根的情况去讨论
根不可变就不断递归去解决它的子树!!! 这个很有意思

高贵的树dp

常见题目:没有上司的舞会 直接来变种 树上背包

#include <bits/stdc++.h>
using namespace std;
long long f[501][501][2];
int v[501];
int n,cnt,m;
struct node {
	int where;
	node *next;
}*head[501],a[501];
void makelist(int x,int y)
{
	a[++cnt].where=y;
	a[cnt].next=head[x];
	head[x]=&a[cnt];
}
void dfs(int i)
{
	for(node *x=head[i];x;x=x->next)
	{
		dfs(x->where);
		for(int j=m;j>=0;j--)
		for(int k=0;k<=j;k++)
		{
			f[i][j][0]=max(f[i][j][0],f[i][j-k][0]+max(f[x->where][k][0],f[x->where][k][1]));
			f[i][j][1]=max(f[i][j][1],f[i][j-k][1]+f[x->where][k][0]);
		}
	}
	for(int j=m;j;j--)
	f[i][j][1]=f[i][j-1][1]+v[i];
	f[i][0][1]=0;
}
int main()
{
	cin>>n>>m;
	int i;
	for(i=2;i<=n;i++)
	{
		int x;
		cin>>x;
		makelist(x,i);
	}
	for(i=1;i<=n;i++)
	cin>>v[i];
	
	dfs(1);
	
	cout<<max(f[1][m][0],f[1][m][1]);
}

这里主要就是有一个限制只能最多取m个人 所以要拿背包的思想做把人当作物品取枚举。故要注意背包的东西最大容量和每个物品最多有多少个!!然后枚举物品时注意枚举顺序!!要从大往下枚举!!这就是一道典型的树上背包题而且是较为简单的类型
2.
这道题就是一个很经典的另一个书上背包题

#include <bits/stdc++.h>
using namespace std;
typedef int64_t i64;
i64 dp[3005][3005];
int siz[3005];//子树大小 !! 
int n,m,cnt;
struct node{
	int v,where;
	node *next;
}a[3005],*head[3005];
int v[3005];
int ans;//计入多少个算进去的非根节点
void makelist(int x,int y,int z)
{
	a[++cnt].where=y;
	a[cnt].v=z;a[cnt].next=head[x];
	head[x]=&a[cnt];
}
void solve(int x)
{
	if(x>n-m)
	{
		siz[x]=1;
		dp[x][1]=v[x];
		return ;
	}
	for(node *y=head[x];y;y=y->next)
	{
		solve(y->where);
		siz[x]+=siz[y->where];
	//	if(x==1)
		for(int i=siz[x];i>=0;i--)
		{
			for(int j=0;j<=siz[y->where];j++)
			{
	//			if(x==1)
	//			{
	//				cout<<y->where<<' '<<y->v<<' ';
	//				cout<<dp[x][1]<<' ';
	//			}
				dp[x][i]=max(dp[x][i],dp[x][i-j]+dp[y->where][j]-(y->v));
			}
		}
	
	}
}
int main()
{
	cin>>n>>m;
	memset(dp,237,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		dp[i][0]=0;
	}
	for(int i=1;i<=n-m;i++)
	{
		int k;//有k个子节点
		cin>>k;
		for(int j=1;j<=k;j++)
		{
			int x,y;
			cin>>x>>y;//节点编号 和 节点的成本  
			makelist(i,x,y);
		}
	}
	for(int i=n-m+1;i<=n;i++)
	{
		cin>>v[i];
	}
	//cout<<dp[1][1];
	solve(1);
	//cout<<siz[5]<<'\n';
	for(int i=m;i>=1;i--)
	{
		if(dp[1][i]>=0)
		{
		//	cout<<dp[1][i]<<' '<<i<<' '<<endl;
			cout<<i;
		return 0;
		}
	}
//	cout<<endl;
//	for(int i=1;i<=n;i++)
//	{
//		for(int j=1;j<=n;j++)
//		{
//			cout<<dp[i][j]<<' ';
//		}
//		cout<<'\n';
//	}
}

去记录目标节点的数目就是用户节点的数目用用户节点的数目取枚举如果该子树有用户siz!=0否则siz==0,故用户数量是一个关键去看枚举的过程
但是如果该数是一个无根树的话

那么就会有限制条件要标记父亲就是不能一直访问的去

#include <bits/stdc++.h>
using namespace std;
int tot;
int siz[100005],st[100005];
int n,cnt,m;
struct node {
	int where;
	node *next;
}*head[100005],a[200010];
int dp[100005][55];
void makelist(int x,int y)
{
	a[++cnt].where=y;
	a[cnt].next=head[x];
	head[x]=&a[cnt];
}
void dfs(int x)
{
	st[x]=1;
	for(node *y=head[x];y;y=y->next)
	{
		if(st[y->where]) continue;
		dfs(y->where);
		siz[x]+=siz[y->where];//这一步不也是同理
		if(siz[y->where]) tot++;
		for(int i=min(m,siz[x]);i>=0;i--)
		{
			for(int j=0;j<=min(siz[y->where],i);j++)
			{
				dp[x][i]=max(dp[x][i],dp[x][i-j]+dp[y->where][j]+(siz[y->where]&&siz[y->where]==j));
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		makelist(x,y);
		makelist(y,x);
	}
	int k;
	cin>>k;
	for(int i=1;i<=k;i++)
	{
		int x;
		cin>>x;
		siz[x]=1;
	}
	dfs(1);
	cout<<2*(tot-dp[1][m]);
}

tot 是总的节点 个数要求的话 节点个数-1为边的个数 故就枚举走了多少条边 若该节点有目标节点则要走的边先对应要++ 之后去不断筛选得出答案

此外还有一种变种题 用递归去实现 子树的区间枚举 原因:给出树的方式很奇特!!如先序给出树的情况,


这道题目便是 那就很显然去用递归的思想去弄 每一个东西去枚举l r
很显然目前还不是特别特别懂!!但是可以化为区间dp 分界线为根 左边区间为左子树 右边区间为右子树!!这样不断枚举的去!!!达到想要的目的!!

#include <bits/stdc++.h>
using namespace std;
typedef int64_t i64;
i64 f[100][100];
int n;
int root[100][100];
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];
		root[i][i]=i;
		f[i][i-1]=0;
	}
	for(int i=1;i<n;i++)
	{
		for(int j=1;j<=n-i;j++)
		{
			f[j][j+i]=f[j+1][j+i]+f[j][j];
			root[j][j+i]=j;
			for(int k=j+1;k<j+i;k++)
			{
				if(f[j][j+i]<f[j][k-1]*f[k+1][j+i]+f[k][k])
				{
					f[j][j+i] = f[j][k-1] * f[k+1][j+i] + f[k][k];
					root[j][j+i]=k;
				}
			}
		}
	}
	cout<<f[1][n]<<'\n';
	print(1,n);
}

故要注意

高贵的换根树dp

如何去dp呢
主要就是两步 该点为根与该点不为根的情况 up从下往上 down 从下往上弄一次!!
这种题目目前写的不多但是都很经典 !! 目前没做到多少题目!!到时候再弄弄把

标签:int,siz,枚举,区间,树型,where,dp
From: https://www.cnblogs.com/--Lance/p/17363245.html

相关文章

  • 设置wordpress:关闭底部默认的facebook等链接(wordpress 6.2)
    一,默认显示:如图:说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest     对应的源码可以访问这里获取: https://github.com/liuhongdi/     或: https://gitee.com/liuhongdi说明:作者:刘宏缔邮箱:[email protected]......
  • 【树形DP入门题】NO337 打家劫舍III
    【树形DP入门题】337.打家劫舍III小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为root。除了root之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。如果两个直接相连的房子......
  • 设置wordpress:设置标题字号大小(wordpress 6.2)
    一,未设置之前字号过大,如图:说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest     对应的源码可以访问这里获取: https://github.com/liuhongdi/     或: https://gitee.com/liuhongdi说明:作者:刘宏缔邮箱:3711253......
  • 设置wordpress:隐藏 自豪地采用WordPress 链接(wordpress 6.2)
    一,未隐藏前的效果说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest     对应的源码可以访问这里获取: https://github.com/liuhongdi/     或: https://gitee.com/liuhongdi说明:作者:刘宏缔邮箱:[email protected]......
  • 安装wordpress 6.2(php 7.4.2)
    一,得到安装包的下载地址:1,官网地址:https://cn.wordpress.org/ 如图:点击获取WordPress按钮2,在下载WordPress6.2按钮上右键,选择: 复制链接地址,复制的链接如下:https://cn.wordpress.org/latest-zh_CN.zip说明:刘宏缔的架构森林是一个专注架构的博客,地址......
  • C#使用委托在Socket Udp端口侦听线程内更新主窗口控件显示
    c#开启线程侦听SocketUDP端口,端口接收到网络读卡器的读卡数据后刷新UI界面显示接收数据,解析数据包信息并向读卡器发送显示文字、驱动读卡器播报语音、蜂鸣响声提示、开启继电器开关等操作。  .net提示通过设置:CheckForIllegalCrossThreadCalls=false,可以在子线程内强制更新......
  • CDN加速WordPress触发CORS导致跨域加载失败
    这两天折腾CDN加速来提升自己博客的访问速度,用的阿里云CDN加速方案;使用的时候发现一个问题,部分资源CDN加速失败,原因是触发了CORS,因为CDN加速网址与博客网址不一致引发的跨域请求不成功;从报错中发现Off与Tff字体加载报错:(index):1AccesstoFontat'http://cdn.5yun.org/wp-conte......
  • 第六届河南省赛 zzulioj 1484: 探 寻 宝 藏 (二维双线DP)nyoj 712
    1484:探寻宝藏TimeLimit: 1Sec  MemoryLimit: 128MBSubmit: 76  Solved: 37SubmitStatusWebBoardDescription传说HMH大沙漠中有一个M*N迷宫,里面藏有许多宝物。某天,Dr.Kong找到了迷宫的地图,他发现迷宫内处处有宝物,最珍贵的宝物就藏在右下角,迷......
  • LAoj 3695 - Distant Galaxy (DP&几何)好题高效
    YouareobservingadistantgalaxyusingatelescopeabovetheAstronomyTower,andyouthinkthatarectangledrawninthatgalaxywhoseedgesareparalleltocoordinateaxesandcontainmaximumstarsystemsonitsedgeshasagreatdealtodowiththemys......
  • wordpress产品排序
    updatewp_postssetmenu_order=100wherepost_type='product';updatewp_postssetmenu_order=5wherepost_name='r-m-williams-craftsman-boot_792c678e';updatewp_postssetmenu_order=10wherepost_name='r-m-williams-rod-polo_ee292998......