UVA1354枚举二叉树

枚举二叉树的方式有很多,其中一种方式就是每次从一个集合中挑选两个点出来,一个做左子树,一个做右子树,直到集合中只剩下一个节点位置

那么问题就在于如何挑选?因为必须创建节点加入集合当中去。

我一开始的思路是使用vecoter,每挑选一个就删除一个,并把新创建的节点加入到vector中去。

但是这种方式是行不通的,因为当回溯的时候,vector不可能恢复原样。所以使用的方法只能是在数组的末尾添加,每挑选一个节点,就设置一次vis,当第cur次挑选的时候,挑选的范围是num_node + cur.这很好理解,第cur次挑选的时候,需要考虑前面cur-1次挑选完后新创建的节点。

本质还是回溯法一共有num_node - 1个步骤,每个步骤的可以选择的节点数是有限制的

需要注意的是,挑出来的两个节点放在左面和右面的结果是不一样的。所以需要考虑到所有的情况(挑选的时候的第二层循环中的j,是从0开始的,而不是从i开始的)

如果左右是一样的,那么可以从i开始

下面附上代码:

//我发现网上写的是些什么玩意
//枚举二叉树
//每次枚举两个节点,并将两个节点合并成一个节点,然后集合中,供下次选取
//使用vector,和vector中的erase函数
//至于汝佳写的枚举子集的方式,先放一放吧,看的不是很懂
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn = 6;

double max_room,room;

int num_node;
struct node
{
    double w;
    double left;
    double right;
    node(double c = 0,double a = 0,double b = 0):w(c),left(a),right(b){}
}Node[1<<maxn];//1<<maxn足够,因为这是一棵有maxn层的完全树,而我们要求的树叶只有maxn层的树。
int vis[1<<maxn];

void DFS(int cur)
{
    if(cur == num_node -1 )
    {
        double now_room = Node[2 * (num_node - 1)].left + Node[2 * (num_node - 1)].right;
        if(now_room > max_room && now_room < room)
        {
            max_room = now_room;
        }
    }
    else
    {
        for(int i = 0;i < num_node + cur;i++)//i挑的是左树,j挑的是右树
        {
            for(int j = 0;j < num_node + cur;j++)
            {
                if(i == j)
                    continue;
                if(!vis[i] && !vis[j])
                {
                    //都没有访问过
                    vis[i] = vis[j] = 1;
                    Node[num_node + cur].w = Node[i].w + Node[j].w;
                    double left = Node[j].w / Node[num_node + cur].w;
                    double right = Node[i].w /Node[num_node + cur].w;
                    Node[num_node + cur].left = max(left + Node[i].left,Node[j].left - right);
                    Node[num_node + cur].right = max(right + Node[j].right,Node[i].right - left);
                    DFS(cur + 1);
                    vis[i] = vis[j] = 0;
                }
            }
        }
    }

}

int main()
{
#ifdef local
    freopen("input.txt","r",stdin);
#endif
    int kase;
    scanf("%d",&kase);
    for(int i = 0;i < kase;i++)
    {
        max_room = 0;
        memset(vis,0,sizeof(vis));
        memset(Node,0,sizeof(Node));
        scanf("%lf%d",&room,&num_node);
        for(int j = 0;j < num_node;j++)
        {
            scanf("%lf",&Node[j].w);
        }
        DFS(0);
        if(max_room == 0)
            printf("-1\n");
        else
            printf("%.12lf\n",max_room);

    }
    return 0;
}

至于汝佳大神的动态规划的实现,我暂时还看不太懂,以后再看吧

原文地址:https://www.cnblogs.com/TorettoRui/p/10471541.html

时间: 2024-10-10 07:39:51

UVA1354枚举二叉树的相关文章

UVa1354 Mobile Computing (枚举二叉树)

链接:http://acm.hust.edu.cn/vjudge/problem/41537分析:二进制法枚举二叉树.用n位二进制位代表n个元素,第i位为1代表集合中包含第i个元素,否则不包含.从右往左依次表示第0,1,2,3...n-1号元素,sum表示包含集合中的元素时的总重量,tree[subset]表示包含集合中的元素时天平合法的L和R,vis表示当前子集是否已经被枚举过避免重复枚举.然后就是dfs递归枚举子集,枚举左子树的集合剩下的就是右子树,然后继续递归枚举,枚举到叶子结点或vis为

UVa 1354 天平难题 (枚举二叉树)

题意: 分析: 其实刚看到这题的时候觉得很难, 以至于结束了第七章然后去做了一遍第六章树的部分.现在再做这题觉得思路并不是太难,因为总共就只有六个结点,那么只要枚举二叉树然后算出天平然后再从叶子往上推就能得出这棵树的宽度.这题我觉得主要难点是如何去枚举二叉树,其实这就是回溯法的核心.先去dfs选这个作为结点的, 然后还原, 再做不选的dfs, 这样就能没有遗漏(但会有重复)地枚举二叉树了. 这题还有个细节是一个天平中,左子树的右长度可能会超过天平右臂 + 右子树的长度, 如下图 那么就不能单纯地

Mobile Computing-天平难题-Uva1354(回溯枚举二叉树)

原题:https://uva.onlinejudge.org/external/13/1354.pdf 有s块石头,每块都被一根绳子吊着,如果有两个及以上的石头,需要平衡的天平把所有的石头挂起来. 房间的宽度为r,问小于房间宽度r的天平的最大宽度. 分析: 是个回溯枚举的问题,枚举中途如果发现当前宽度已经大于r,回溯. 难点: 也可以说是亮点,就是枚举所有的二叉树,一个天平可以看成是一个二叉树. 具体点说递归建立二叉树的过程就是每次从包含所有节点的集合中选择两个节点,合二为一 所以我们建树的过程

POJ - 3295 - Tautology = 枚举 + 二叉树遍历

http://poj.org/problem?id=3295 题意:给若干个小写字母表示bool变量,大写字母表示bool运算,求这个表达式的是否为永真表达式. 输入形如: ApNp ApNq 也就是前缀表达式. 所以就写个东西遍历它构造一棵树,然后给同名变量枚举赋值,假如没有任何赋值使得树根输出0,则为永真表达式. 考察二叉树的递归遍历. #include<algorithm> #include<cmath> #include<cstdio> #include<

UvaLive 3403 Mobile Computing 枚举二叉树

题目链接:点击打开链接 题意: 给定房间宽度r和s个石头重量 设计一个尽量宽但宽度不超过房间宽度r的天平,使得能把所有石头放在天平上 (天平的一端要么挂一个石头,要么挂一个天平) 天平的平衡满足杠杆原理(两端重量的比值与两端距离悬挂天平点的距离成反比,每根天平杆长度为1) 输出最大的宽度(若不能把石头都挂上输出-1) 思路: 枚举计算每一个状态时的最大宽度. 若这个状态只有一个石头,那么得到的天平就认为是(0,0) (0,0)的意思是以天平支点为准,向左延展的长度和向右延展的长度 否则一定是由2

uva1354 天平难题 解题报告

uva1354 天平难题.主要有 回溯法,二叉树模拟. 当然,这道题也有很多剪枝,但是这个用二叉树性质模拟的数组应该过了,这样写,这道题,完全就足够了. 原题目链接:https://uva.onlinejudge.org/external/13/1354.pdf 题目大意: 就是首先给你一个房间的宽度r,之后有s个挂坠,第i个挂坠的重量是wi,设计一个尽量宽,但是不能宽过房间.的天平.当然会有好几组这样的数据. 这些天平棍的长度都为1,天平棍要么挂 挂坠,要么就继续挂木棍设挂的木棍必须要让天平平

1354 Mobile Computing(暴力、二进制枚举、简直无情)

翘了3节课来A这道题,最后还超时了,也是蛮拼的.. 没做出来主要一个方面就是不会一个二进制数子集的枚举 这里上一下代码: for(int S0 = S; S0; S0 = (S0 - 1) & S){ } 这里S0就是S的子集了~! 题目的思路就是枚举所有情况,注意记忆化[话说这题学到了不少] #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using n

uvalive 6669 hidden tree(好壮压dp)

题目见here 题意:给一个序列arr[],你从中选择一些子序列,将子序列的值从左往右依次放到某棵二叉树的叶子节点上,使得除了叶子,所有节点左右子树权和相等.子树的权和 = 子树叶子的权和.如果存在这样一棵二叉树,选择的子序列就是合法的.问,最长的合法子序列是多少. 思路: 枚举二叉树可能的叶子的最小权(入手点),显然,能和此数一起组成二叉树的数,要么和这个数相等,要么是这个数的2^k倍.把满足这种关系的数,认做一个集合,显然集合外的数,不能和集合内的数组成二叉树.那么,我们只需要一个一个得求出

uva 1354 Mobile Computing ——yhx

1 #include<cstdio> 2 #include<cmath> 3 #include<cstring> 4 struct node 5 { 6 int fat,lson,rson; 7 double wei; 8 }tree[500]; 9 double w[10],lim,ans; 10 int n; 11 double max(double x,double y) 12 { 13 return x>y?x:y; 14 } 15 void calc_s