2016.7.13 树形动规

    RMQ算法讲完没几个小时,我们就开始了小紫中的一节(足见这个算法很重要)——树形动归;

参考资料:

    1.焦作一中信息学oy(http://www.cnblogs.com/gq-ouyang/archive/2013/02/26/2933431.html

    2.小黄(信息学奥赛一本通)

    3.小紫(算法竞赛入门经典第二版)

题型总结:(来自资料1

    1、加分二叉树:区间动规+树的遍历;

    2、二叉苹果树:二叉树上的动规;

    3、最大利润:多叉树上的动规;

    4、选课:多叉树转二叉;

    5、选课(输出方案):多叉转二叉+记录路径;

    6、软件安装:判断环+缩点+多叉转二叉;

学习目标:

    1.熟练掌握树形动规(小紫刷掉,资料一刷掉);

    2.复习树的相关知识;

    (二叉树的分支确定且可以为空,具有良好的递归特性,程序设计很方便,故常将多叉树转二叉树(小黄))

一.加分二叉树

题意:树的结构动规(!=树形动规,是根据树的结构的动规)(n<30节点),(分数<100)

题目分析:分左右,要熟练遍历顺序;老师没让写......代码是资料一里的

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<memory>
#include<algorithm>
#include<string>
#include<climits>
#include<queue>
#include<vector>
#include<cstdlib>
#include<map>
using namespace std;

const int ee=50,e=-999999999;
int n;
int a[ee]={0},f[ee][ee],root[ee][ee]={0};//f(i,j)中序遍历为i,i+1,…,j的二叉树的最大加分

//**若根节点的下表是k,则左端点的是k-1,右端点是k+1;
void front(int x,int y)
{
    if(root[x][y]!=0)
        cout<<root[x][y]<<' ';
    if(root[x][root[x][y]-1]!=0)    front(x,root[x][y]-1);
    if(root[root[x][y]+1][y]!=0)    front(root[x][y]+1,y);
}

int main()
{
//freopen("in.in","r",stdin);


    //memset 赋初值不能为1 memset(f,1,sizeof(f));
    cin>>n;
    
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
            f[i][j]=1;
    }
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i][i]=a[i];
        root[i][i]=i;
    }
    //区间长度
    for(int len=1;len<=n;len++)
    {
        //区间起点
        for(int i=1;i<=n;i++)
        {
            //终点
            int j=i+len;
            if(j<=n)
            {
                int temp=e;
                //因为是中序排列
                for(int k=i;k<=j;k++)
                {
                    if(temp < (f[i][k-1]*f[k+1][j]+a[k]))
                    {
                        temp=f[i][k-1]*f[k+1][j]+a[k];
                        root[i][j]=k;
                    }
                }
                f[i][j]=temp;
            }
        }
    }
    cout<<f[1][n];
    
    //前序遍历
    cout<<endl;
    front(1,n);

    
fclose(stdin);fclose(stdout);
return 0;
}

二.URAL_1018 二叉苹果树

题意:每个边(共n)带权值,给定保留边数目(q),算最大权值;1<=Q<= N,1<N<=100

题目分析:这里用了一个后来都会用到的思想,就是因为边没有点好记,所以可以把边上的分数记在点上,并且给根节点加一条边,这样边数加一以后就不需要分开讨论了;其中createtree递归由上而下不太好理解,我写错了很多次;

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;
int n,f[105][105],vis[105],sta[105],fin[105],amo[105],q;
struct node
{
    int apples,lefloc,rigloc; 
}thran[105];
void createtree(int k)
{
    vis[k]=1;
    for(int i=1;i<=n-1;i++)
    if(sta[i]==k||fin[i]==k)
    {
        int t=sta[i]+fin[i]-k;//被确认的点是子节点 
        if(vis[t])continue;//自上而下判重; 
        createtree(t);//把建树向下传递递归
        if(!thran[k].lefloc)
        {
        thran[k].lefloc=t;//传递节点 
        thran[t].apples=amo[i]; 
        }
        else {//用lefloc就是提醒自己,这个和线段树不一样,直接记节点 
        thran[k].rigloc=t; 
        thran[t].apples=amo[i]; break;}
    }
}
void dp(int k)
{
    if(thran[k].lefloc)
    {
        int lef=thran[k].lefloc;
        int rig=thran[k].rigloc;
        dp(lef);
        dp(rig);
        for(int i=1;i<=q+1;i++)//还可以再简化,因为在除根节点以外不可能取满 
        for(int j=0;j<=i-1;j++)//一边可为0 
        f[k][i]=max(f[k][i],f[lef][j]+f[rig][i-j-1]+thran[k].apples); //必选根 
    } 
    else 
    {
        f[k][1]=thran[k].apples;
        f[k][0]=0;
    }
} 
int main()
{
    freopen("apple.in","r",stdin);
    freopen("apple.out","w",stdout);
    scanf("%d %d",&n,&q);
    for(int i=1;i<=n-1;i++)//n-1条边 
    scanf("%d %d %d",&sta[i],&fin[i],&amo[i]);
    createtree(1);//从1建树确保了后来的上下确定
    dp(1) ;//递归动规 
    cout<<f[1][q+1];
    return 0;
}

 

原文地址:https://www.cnblogs.com/SindarDawn/p/5668300.html