当前位置: 首页 > news >正文

树与并查集,区间dp,树形dp

树与并查集

就是想求连通子集的个数


#include<iostream>
using namespace std;

const int MAX = 1000010;			//数组的最大长度(即图中点个数的最大值)
int m, n;						//当前图的长宽规格
int pre[MAX];					//用于存放每个点的根节点

void fun1(int n)				//初始化函数
{
	for (int i = 1;i <= n;i++)
		pre[i] = i;
}
int fun2(int key)		   //寻找根节点函数
{
	if (pre[key] == key) return key;
	return pre[key] = fun2(pre[key]);
}
void fun3(int x, int y)		   //联合函数
{
	int rootx = fun2(x);
	int rooty = fun2(y);
	if (rootx != rooty) pre[rootx] = rooty;
}

int main()
{
	int x, y, line;
	cin >> m >> n >> line;
	fun1(m * n);
	for (int i = 0;i < line;i++)
	{
		cin >> x >> y;
		fun3(x, y);
	}
	int ans = 0, a[MAX] = { 0 };
	for (int i = 1;i <= m * n;i++)
		a[fun2(i)] = 1;
	for (int i = 1;i <= m * n;i++)
		if (a[i]) ans++;
	cout << ans << endl;
	return 0;
}

已知前序遍历顺序,即根节点即为该序列的第一个结点,每次获取第一个结点就可以将该子树分为左右子树,最后再分别递归左右子树,因为是要输出后序遍历顺序,所以先递归左子树,再递归右子树,最后将标记的根节点输出

# include<iostream>
# include<string>
# include<cstring>
# include<cstdio>
using namespace std;
string a, b;
void work(string b,string a) {
	if (b.empty()) return ;
		char root = b[0];
		int k = a.find(root);
		b.erase(b.begin());
		string bleft = b.substr(0, k);
		string aleft = a.substr(0, k);
		string bright = b.substr(k);
		string aright = a.substr(k + 1);
		work(bleft, aleft);
		work(bright, aright);
		cout << root;
}
 
int maim() {
	cin >> a >> b;
	work(b, a);
	cout << endl;
	return 0;
}

 

就是二叉树的前序遍历

# include<iostream>
# include<string>
using namespace std;
int n;
char t, tt;
 
struct tree {
	char l;
	char r;
};
tree node[300];
void work(char x) {
	if (x == '*')return;
	cout << x;
	work(node[x].l);
	work(node[x].r);
}
int main() {
	cin >> n;
	cin >> t;
	char a, b;
	cin >> a >> b;
	node[t].l = a, node[t].r = b;
	for (int i = 2; i <= n; i++) {
		cin >> tt;
		cin >> node[tt].l >> node[tt].r;
	}
	work(t);
	return 0;
}

跟此类第一题一样 

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
void beford(string in,string after){
    if (in.size()>0){
        char ch=after[after.size()-1];
        cout<<ch;//找根输出
        int k=in.find(ch);
        beford(in.substr(0,k),after.substr(0,k));
        beford(in.substr(k+1),after.substr(k,in.size()-k-1));
    }
}
int main(){
    string inord,aftord;
    cin>>inord;cin>>aftord;//读入
    beford(inord,aftord);cout<<endl;
    return 0;
}

 区间dp

区间DP的核心在于将一个大区间分解成小区间,并通过处理小区间来求解大区间的问题。这种方法通常涉及两个步骤:记忆化搜索和递推。在递推过程中,关键是确定循环的顺序和状态转移方程。大多数区间DP问题都可以通过确定大区间如何转化为小区间,找出边界条件,然后进行动态规划求解。

memset(dp,0,sizeof(dp))//初始dp数组
for(int len=2;len<=n;len++){//枚举区间长度
    for(int i=1;i<n;++i){//枚举区间的起点
        int j=i+len-1;//根据起点和长度得出终点
        if(j>n) break;//符合条件的终点
        for(int k=i;k<=j;++k)//枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
    }
}
         

 令dp[i][j]表示石子中第i堆到第j堆合并的最小代价,则 j~ends 堆合并 = 较小的(原来, 分割点i坐部分重量 + 分割点i右边部分重量 + 合并后两堆总重量),该问题的转移方程可以总结如下:

dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+weigth[i][ends]);

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f
int stone[105];
int dp[105][105];
int sum[105];
int main()
{
    int n;
    scanf("%d",&n);
    memset(sum,0,sizeof(sum));
    memset(dp,INF,sizeof(dp));
    for(int  i =1;i<=n;i++){
        scanf("%d",&stone[i]);
        sum[i] = sum[i - 1] + stone[i];//重量
        dp[i][i] = 0;
    }
    for(int len = 1;len<=n;len++){//枚举长度
        for(int j = 1;j+len<=n+1;j++){//枚举起点,ends<=n
            int ends = j+len - 1;
            for(int i = j;i<ends;i++){//枚举分割点
                dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+sum[ends]-sum[j-1]);//更新状态
            }
        }
    }
    cout<<dp[1][n]<<endl;
    return 0;
}

环状以后合并区间的情况就可以从后往前合并,最后合并完成可能是1~n,2~n~1,3~n~2.....这种n个石子合并的情况。所以我们可以破环成链,将前n-1各元素也放到n后面构成一个线性的环状序列,在对这个序列进行线性dp即可

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f
int stone[105];
int dpmin[205][205];//最小
int dpmax[205][205];//最大
int sum[205];
int main()
{
    int n;
    scanf("%d",&n);
    memset(sum,0,sizeof(sum));
    memset(dpmin,INF,sizeof(dpmin));
    memset(dpmax,-1,sizeof(dpmax));
    for(int  i =1;i<=n;i++){
        scanf("%d",&stone[i]);
        sum[i] = sum[i - 1] + stone[i];
        dpmin[i][i] = 0;
        dpmax[i][i] = 0;
    }
    for(int i = 1;i<=n;i++){
        sum[i+n] = sum[i+n-1]+stone[i];//展开的n后面的n-1~1重量
        dpmin[i+n][i+n] = 0;
        dpmax[i+n][i+n] = 0;
    }
    for(int len = 1;len<=n;len++){//长度还是最大n
        for(int j = 1;j+len<=2*n;j++){//起点枚举最大到2*n-1,ends<=2*n-1
            int ends = j+len - 1;
            for(int i = j;i<ends;i++){//注意!i<ends!!!因为i=ends时,dp[ends+1][ends]是不成立的!
                dpmin[j][ends] = min(dpmin[j][ends],dpmin[j][i]+dpmin[i+1][ends]+sum[ends]-sum[j-1]);
                dpmax[j][ends] = max(dpmax[j][ends],dpmax[j][i]+dpmax[i+1][ends]+sum[ends]-sum[j-1]);
            }
        }
    }
    int ansmin = 0xfffffff;
    int ansmax = -1;
    for(int i = 1;i<=n;i++){
        ansmin = min(ansmin,dpmin[i][i+n-1]);//找1~n,2~n~1,3~n~2....的合并n个堆的中最大和最小的值
        ansmax = max(ansmax,dpmax[i][i+n-1]);
    }
    cout<<ansmin<<endl;
    cout<<ansmax<<endl;
    return 0;
}

树形dp

在树形DP中,我们通常会先处理子树的问题,然后再将子树的解合并到父节点上。这个过程类似于树的后序遍历,即先访问所有子节点,然后再访问父节点。例如,我们可以定义状态f[i]表示以节点i为根的子树的某种性质,然后通过子节点的状态来更新父节点的状态。

# 定义树的节点数量
n = 10

# 初始化树的边和节点权值
edges = [(1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)]
weights = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 初始化状态数组
f = [[0 for _ in range(2)] for _ in range(n + 1)]

# 定义递归函数来进行树形DP
def dfs(u, parent):
f[u][0] = 0
f[u][1] = weights[u]
for v in tree[u]:
if v == parent:
continue
dfs(v, u)
f[u][0] += max(f[v][0], f[v][1])
f[u][1] += f[v][0]

# 构建树的邻接表
tree = [[] for _ in range(n + 1)]
for u, v in edges:
tree[u].append(v)
tree[v].append(u)

# 从根节点开始递归
dfs(1, -1)

# 输出最终结果
print(max(f[1][0], f[1][1]))

就是按模板写

#include<iostream>
#include<math.h>
#include<string.h>
using namespace std;
const int N=6000+10;
int last[N];
int ne[N],edge[N],cnt=1;
bool biao[N];
void add(int a, int b){
    edge[cnt] = b;
    ne[cnt] = last[a];
    last[a] = cnt++;
}
int dp[N][2];
int a[N];
void dps(int root)
{
   dp[root][0]=0;//不选root号节点;
   dp[root][1]=a[root];//选root号节点
   for(int i=last[root];i>=1;i=ne[i])
   {
       int j=edge[i];
       dps(j);
       dp[root][0]+=max(dp[j][0],dp[j][1]);
       dp[root][1]+=dp[j][0];
   }
}

int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
    cin>>a[i];
  //因为0包含的有边
  int x,y;
  for(int i=1;i<n;i++)//n-1条边
  {cin>>x>>y;//y是x的直接上司
    add(y,x);
    biao[x]=true;//a有父节点b
  }
  int v=1;
  while(biao[v])v++;//找到没有父节点的点
  dps(v);
    cout<<max(dp[v][1],dp[v][0]);
}

 

相关文章:

  • 2025.2.23机器学习笔记:PINN文献阅读
  • 【react18】如何使用useReducer和useContext来实现一个todoList功能
  • 通过Selenium实现UI自动化校验输入的数据是否完整有效方式
  • Unity Mixamo模型更好的适配角色模型
  • 【Gin-Web】Bluebell社区项目梳理2:JWT-Token认证
  • 【深度学习】自然语言处理(NLP)-语音识别-WaveNet
  • 虚函数与多态
  • 蓝桥云客 路径之谜
  • 算法——Boyer-Moore算法
  • 网络安全知识--网络、网络安全产品及密码产品概述
  • 最新版免安装免费动图录制与编辑利器
  • 知识库技术栈
  • css特异性,继承性
  • round的意思
  • Windows 图形显示驱动开发-查询 WDDM(3.2) 功能支持和启用
  • Maven导入hutool依赖报错-java: 无法访问cn.hutool.core.io.IORuntimeException 解决办法
  • QML ToolButton与DelayButton的使用、详解与自定义样式
  • Day32.
  • Linux 内核自旋锁spinlock(四)--- queued spinlock
  • ubuntu环境编译ffmepg支持nvidia显卡加速
  • 做网站开发的经营范围/东莞网络营销信息推荐
  • 内蒙古网站建设/网络销售推广平台
  • 怎么自己在家做网站/成都百度推广开户公司
  • 邢台网站建设多少钱/seo经典案例
  • 六合哪家做网站建设/google图片搜索引擎入口
  • 免费主题wordpress/seo研究中心怎么了