首页 > 其他分享 >树状DP心得

树状DP心得

时间:2024-02-17 12:22:25浏览次数:26  
标签:cnt lc 树状 int dp rc 心得 节点 DP

说一下近日所学的主要两种题型,一个是分叉情况问题,一种是树上背包问题。
分叉情况问题
具体的题可以参考小胖守皇宫和三色二叉树。

点击查看代码
小胖守皇宫
#include<bits/stdc++.h>
using namespace std;
const int N=2000; 
vector<int>son[N];
int fa[N],h[N],f[N][3];
//f[0]守自己,f[1]父亲守,f[2]儿子守 
int n;
void dp(int x)
{
	int tot=0x3f3f3f3f;
	if(f[x][0])
	{
		return ;
	}
	if(son[x].size()==0)
	{
		f[x][0]=h[x];
		f[x][1]=0;
		f[x][2]=0x7ffffff;	
		return ;
	}
	f[x][0]=h[x];
	for(int i=0;i<son[x].size();i++)
	{
		int y=son[x][i];
		dp(y);
	}
	for(int i=0;i<son[x].size();i++)
	{
		int y=son[x][i];
		f[x][0]+=min(min(f[y][0],f[y][1]),f[y][2]);
		f[x][1]+=min(f[y][0],f[y][2]);
		f[x][2]+=min(f[y][0],f[y][2]);
		tot=min(tot,f[y][0]-f[y][2]);//求自己守和让儿子守的差值
		
	}
	if(tot>0)//无儿子守,强迫差值最小的儿子守
	{
		f[x][2]+=tot;
	}
}
int main()
{
//	memset(f,0x3f,sizeof(f));
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int id,cost,num;
		cin>>id>>cost>>num;
		h[id]=cost;
		for(int j=1;j<=num;j++)
		{
			int x;
			cin>>x;
			son[id].push_back(x);
			fa[x]=id;
		}
	}
	int root;
	for(int i=1;i<=n;i++)
	{
		if(fa[i]==0)
		{
			root=i;
			break;
		}
	}
	dp(root);
//	cout<<f[root][0]<<endl;
	cout<<min(f[root][0],f[root][2]);
}
点击查看代码
三色二叉树
#include<bits/stdc++.h>
using namespace std;
const int N=20000;
struct lmy
{
	int lc,rc;
}a[N];
char s[N];
int f[N][3];//最多 
//f[0]该节点为绿色,绿色总数 
//f[1]该节点为红色,绿色总数 
//f[2]该节点为蓝色,绿色总数
int g[N][3];//最少 
int cnt;
int build()
{
	int u=++cnt;
	if(s[cnt]=='2')
	{
		a[u].lc=build();
		a[u].rc=build();
	}
	else if(s[cnt]=='1')
	{
		a[u].lc=build();
	}
	return u;
}
void big(int u)
{
	if(a[u].lc==0&&a[u].rc==0)//reach the bottom 
	{
		f[u][0]=1;
		return ;
	}
	else if(a[u].lc!=0&&a[u].rc!=0)//has two child
	{
		big(a[u].lc);
		big(a[u].rc);
	}
	else//only has one child
	{
		big(a[u].lc);
	}
	int x=a[u].lc;
	int y=a[u].rc;
	f[u][0]=max(f[x][1]+f[y][2],f[x][2]+f[y][1])+1;
	f[u][1]=max(f[x][0]+f[y][2],f[x][2]+f[y][0]);
	f[u][2]=max(f[x][0]+f[y][1],f[x][1]+f[y][0]);
}
void small(int u)
{
	if(a[u].lc==0&&a[u].rc==0)//reach the bottom 
	{
		g[u][0]=1;
		return ;
	}
	else if(a[u].lc!=0&&a[u].rc!=0)//has two child
	{
		small(a[u].lc);
		small(a[u].rc);
	}
	else//only has one child
	{
		small(a[u].lc);
	}
	int x=a[u].lc;
	int y=a[u].rc;
	g[u][0]=min(g[x][1]+g[y][2],g[x][2]+g[y][1])+1;
	g[u][1]=min(g[x][0]+g[y][2],g[x][2]+g[y][0]);
	g[u][2]=min(g[x][0]+g[y][1],g[x][1]+g[y][0]);
}
int main()
{
	cin>>s+1;
	build();//建图 
	big(1);
	cout<<max(max(f[1][0],f[1][1]),f[1][2])<<" ";
	small(1);
	cout<<min(min(g[1][0],g[1][1]),g[1][2]);
}

不难发现这两个代码的共同点,作为dp,f[i][j]的j总是表示其中可能对结果造成影响的情况分支。因此这种问题的关键除了状态转移方程外,就是去确定可能并可行的分支。其中的关键点就是要找到会对结果造成影响的分支并使分支尽可能的少,例如三色二叉树关于红色和蓝色的情况对这道题没有影响,因此我们可以忽略它。
还有一个确定状态转移方程的小技巧,先抓住其中一个节点分析(不是树根或树叶),以这个节点单独分析,不去考虑其他节点的变化,这样就不容易被头疼的递归搞晕。然后是看叶子节点,按照推出的动态转移方程看看有什么差异(主要是看需不需要赋初始值),最后看一看边界情况和特殊处理就可以了。
接着说一下这两个题的特殊知识点

vector容器 推荐学习链接:传送门
这个内容十分详细

build()建树 直接看三色二叉树代码就可以。

树上背包问题
例题可以看the more the better,选课和苹果二叉树。
the more the better和选课类似,代码只展示选课。

选课

点击查看代码
#include <iostream>
#include <cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2500;
int cnt,to[N],nxt[N],h[N];
int n,m,s[N],f[N][N];
void add(int a,int b)
{
	cnt++;
	to[cnt]=b;
	nxt[cnt]=h[a];
	h[a]=cnt;
}
void dp(int z)
{
	for(int i=h[z];i;i=nxt[i])
	{
		int y=to[i];
		dp(y);
		for(int j=m;j>=0;j--)
		{
			for(int k=1;k<=j;k++)
			{
				f[z][j]=max(f[z][j],f[y][j-k]+f[z][k]);
			}
		}
	}
}
int main()
{
	cnt=0;
	memset(f,0,sizeof(f));
	memset(to,0,sizeof(to));
	memset(nxt,0,sizeof(nxt));
	memset(h,0,sizeof(h));
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,i);
		f[i][1]=b;
	}
	m++;
	dp(0);
	cout<<f[0][m]<<endl;
} 

苹果二叉树

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2500;
int cnt,to[N],w[N],nxt[N],h[N];
int n,m,s[N],f[N][N];
void add(int a,int b,int c)
{

	to[cnt]=b;
	w[cnt]=c;
	nxt[cnt]=h[a];
	h[a]=cnt++;

}
void dp(int z,int fa)
{
	for(int i=h[z];~i;i=nxt[i])
	{
		
		int y=to[i];
		if(y==fa)
		{
			continue;
		}
		dp(y,z);
		for(int j=m;j>=0;j--)
		{
			for(int k=0;k<j;k++)
			{
				f[z][j]=max(f[z][j],f[z][j-k-1]+f[y][k]+w[i]);
			}
		}
	}
}
int main()
{
	memset(h,-1,sizeof(h));
	cin>>n>>m;
	for(int i=1;i<=n-1;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dp(1,-1);
	cout<<f[1][m];
} 

奉上模板,注有解释
点击查看代码
void dp(int u)
{
	for(int i=h[u];i;i=nxt[i])//通过链式结构遍历儿子节点 
	{
		int son=to[i];
		dp(son);
		for(int j=m-v[u];j>=0;j--)
		//枚举背包容积,同背包问题,但要保留该节点
		//该节点是其他节点的前提,必选 
		{
			for(int k=0;k<=j;k++)
			//决策点,k不断更新状态
			//已遍历的儿子节点+正在遍历的儿子节点
			//f[u][j-k]为已遍历的儿子节点背包容积为j-k的最优解
 
			{
				f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
			}
		}
	}
	for(int i=m;i>=v[u];i--)//添上该节点 
	{
		f[u][i]=f[u][i-v[u]]+w[u];
	}
	for(int i=0;i<v[u];i++)
	//如果该节点选不了,它的儿子节点也无法选 
	//无法选的置0 
	{
		f[u][i]=0;
	}
}

总结
树状DP与递归的关系密不可分,但我们在做题的时候可以先将其忽略,以免其对我们的思维造成影响。树状DP的这两个题型就到这里,拜拜。

标签:cnt,lc,树状,int,dp,rc,心得,节点,DP
From: https://www.cnblogs.com/zhengchenxi/p/18017872

相关文章

  • DPInst64.exe difxapi64.dll 是什么 为什么
    DPInst64.exe:安装和卸载驱动程序包。默认情况下,该工具将搜索当前目录,并尝试安装找到的所有驱动程序包。用法:C:\Users\Administrator\Desktop\dp\dp\DPInst64.exe[/UINF文件][/S|/Q][/LM][/P][/F][/SH][/SA][/A][/PATH路径][/EL][/L语言ID][/C][/D][/LogTitle标题][/SW][/?......
  • 树状数组
    树状数组背景由于\(OIer\)们对于数据更高效的储存、修改和查询的需要,一种数据结构树状数组营运而生。介绍树状数组是一个查询和修改时间复杂度都为\(O(log(n))\)的数据结构,主要用于:数组的单点修改和区间查询在使用前缀和求区间和的算法中:如果可以做到在\(O(1)\)......
  • 空间转移(dp+倍增+Floyd)
    第2题   空间转移 查看测评数据信息给定一个n个点,m条边的有向图,编号从1到n,所有边权值是1,现在有一个动点从点1开始一动,其每秒钟可以移动2的k次方千米(k是任意自然数,且2的k次方不超过1000000000)。最少需要几秒才能到达n号点。数据保证1到n至少有一条路径。n⩽50,m⩽1000......
  • 树状数组-三色二叉树 题解
    题目在这里————————————————————————————————三色二叉树首先题面写的很清楚了是一道树状数组题因为这题的输入方式很特别按二叉树序列所以在输入上要特殊处理如下voidread(intx){//读入+存图以左右子树为形式如l[x]=y即y为x左子树......
  • ThreadPoolTaskExecutor以及通过注解实现异步任务
    ThreadPoolTaskExecutor是Spring框架的线程池,实现方式如下:1//声明一个name为asyncTaskExecutor的线程池bean到容器中2@Bean("asyncTaskExecutor")3publicExecutorgetAsyncExecutor(){4ThreadPoolTaskExecutorthreadPoolExecutor=newThreadPoolTaskExecuto......
  • 状压 dp 与 插头 dp
    矩阵上的dp:按格dp/轮廓线dp设\(f[i,j,S]\)表示考虑到第\(i\)行第\(j\)个格子,轮廓线上所有的格子的状态。复杂度为\(\Theta(nm|S|)\)。按行dp\(f[i,S]\)表示选完前\(i\)行的合法方案数。总复杂度为\(\Theta(n|S|^2)\)。P3226[HNOI2012]集合选数给定集合......
  • [学习笔记]换根 DP 的常用处理方式
    [学习笔记]换根DP的常用处理方式换根DP,又称作二次扫描法,通常用于“对每个根求出树形DP的答案”。以每个点作为根节点进行一次树形DP的代价通常无法承受,因此我们会使用两次DFS:第一次DFS指定一个点为根节点,运行一次常规的树形DP。第二遍DFS进行换根DP,得到将根转移......
  • DP 做题记录
    复杂DP各种巨大DP。CF123CBrackets题意:括号数组是一个只有“(”或“)”两类字符的二维数组。括号数组中的合法路径只能从任意位置开始,向右或向下移动。如果一个n×m括号数组中从(1,1)到(n,m)的所有路径经过的字符构成的字符串均为可以完全匹配的括号序列,则这个括号......
  • DP 优化
    DP优化一些典型或者不典型的DP优化实例。CF354DTransferringPyramid题意:有一个\(n\)层的金字塔,现在能进行两种操作,一是给某个点染色,代价是3,或者画一个底边是金字塔底边的子三角形,给每个点,代价是点数+2,要求用最小代价覆盖所有要染色的\(m\)个点。思路:定义\(h_i\)表......
  • 【无评级杂题】DP/贪心
    题目这题后来看了看网上的思路,发现贪心就能做,亏我还写了个O(2*N)的DP...浪费时间了属于是my-code#include<iostream>#include<cstring>#include<string>#include<cstdio>#include<cstdlib>#include<algorithm>#include<stack>#include<queue>......