动态规划,dp

动规分类

  1. 线性动规
  2. 区间动规
  3. 树形动规

区间动规
根据题目要求,全局最优满足局部最优;

典型题例
加分二叉树(洛谷1040)
题目介绍
题目描述
设一个n 个节点的二叉树T 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。
每个节点都有一个分数(均为正整数),记第j 个节点的分数为dj。二叉树T 及它的每个子树都有 一个加分,任意一棵子树S(包括T 本身)的加分等于S 的左子树的加分×S 的右子树的加分+S的根的分数。 若某棵子树为空,规定其加分为1。叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树T。要求输出T 的最高加分和前序遍历。
输入格式
第1 行:一个整数n(n<30),为节点个数。
地2 行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
输出格式
第1 行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2 行:n个用空格隔开的整数,为该树的前序遍历。

输入样例
5
5 7 1 2 10
输出样例

145
3 1 2 4 5

题目分析
由于是中序遍历,因此永远满足左子树节点编号<根节点编号<右子树节点编号,虽是树形,但不存在选择与决策问题,因此不是树规;
在全局最优的同时,区间最优同时满足,因此选用区间dp;

动态转移方程
f[r,l]=max{f[i,k] * f[k,j]+a[i][i]}

#include<cstdio>
#include<iostream>
using namespace std;
int a[20][20],r[20][20];//r数组存储区间根节点
int n;
void find(int x,int y)//前序遍历,dfs顺序 
{
    if(x<=y)
    {
        printf("%d",&r[x][y]);
        find(x,r[x][y]-1);
        find(r[x][y]+1,y);
    }
    return;
}
int main()//区间dp,局部最优满足全局最优,当前决策具有后效性 
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            a[i][j]=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i][i]);
        r[i][i]=i;
    }
    for(int i=n;i>=1;i--)//a[i][j]枚举的是从i到J的顶点; 
        for(int j=1+i;j<=n;j++)//枚举区间首,尾
            for(int k=i;k<=j;k++)
            {
                if(a[i][j]<a[i][k-1]*a[k+1][j]+a[k][k])//同理,枚举区间i~k-1,k+1~j的最大值,再加上顶点自身值 
                {
                    a[i][j]=a[i][k-1]*a[k+1][j]+a[k][k];
                    r[i][j]=k;//将k定为当前区间根节点 
                }
            }
    printf("%d",a[1][n]);//输出的是从1~n的区间最大值; 
}

线性动规

典型题例
导弹拦截,合唱队形;

以下是最长上升子序列最长下降子序列模板

#include<cstdio>
#include<iostream>
using namespace std;
int a[10001],b[10001],c[10001];

int main()
{
    int n=1;
    int maxn=0,mine=0;
    while(scanf("%d",&a[n])){n++;}n--;
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)//最长上升
    {
        b[i]=1;
        for(int j=1;j<=i-1;j++)
            if((a[i]>=a[j])&&(b[j]+1>b[i]))b[i]=b[j]+1;
            if(b[i]>maxn)maxn=b[i];
    }
    for(int i=n;i>=1;i--)//最长下降
    {
        c[i]=1;
        for(int j=i+1;j<=n;j++)
        {
            if((a[i]<a[j])&&c[j]+1>c[i])c[i]=c[j]+1;
        }
        if(mine<c[i])mine=c[i];
    }
    printf("%d%d",maxn,mine);
    return 0;
}

二分优化的最长上升子序列

#include<cstdio>
#include<iostream>
using namespace std;
int n,top,a[100005],t;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&t);
        if(t>a[top])
        a[++top]=t;
        else
        {
            int low=1,high=top,mid;
            while(low<=high)
            {
                mid=(low+high)/2;
                if(a[mid]<t)
                low=mid+1;
                else
                high=mid-1;
            }
            a[low]=t;
        }
}
    printf("%d",top);
}

树状动规

没有上司的晚会

题目介绍
有个公司要举行一场晚会。
为了能玩得开心,公司领导决定:如果邀请了某个人,那么一定不会邀请他的上司
(上司的上司,上司的上司的上司……都可以邀请)。

每个参加晚会的人都能为晚会增添一些气氛,求一个邀请方案,使气氛值的和最大。

input
第1行一个整数N(1<=N<=6000)表示公司的人数。
接下来N行每行一个整数。第i行的数表示第i个人的气氛值x(-128<=x<=127)。
接下来每行两个整数L,K。表示第K个人是第L个人的上司。
输入以0 0结束。

output
一个数,最大的气氛值和。

input:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0

output:
5

思路分析
本题满足树的特征,即有向无环,可以有多个后继,但只能有一个前驱,
最优化问题,当前节点的选择只与其儿子有关,满足无后效性,可以选择树状dp,于是建树,DFS;

#include<iostream>
#include<cstdio> 
using namespace std;
int f[6001][2],father[6001];
bool v[6001];
int n;
int find_max(int a,int b)
{
    if (a>b) return a;
    else return b;
}
void dfs(int k)//f[i][0]储存不选用当前人的最大值,f[i][1]储存选用的最大值 
{
    v[k]=false;
    for (int i=1;i<=n;i++)
        if (v[i] && father[i]==k)
        {
            dfs(i);
            f[k][0]+=f[i][1];
            f[k][1]+=find_max(f[i][0],f[i][1]);
        }
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&f[i][0]);
    int l,k,root;
    root=0;bool bk=true;
    while (scanf("%d%d",&l,&k),l+k>0)
    {
        father[l]=k;
        if (root==l || bk==true)//由于输入的无序性,因此必须找出根节点 
        {
            root=k;bk=false;//根节点等于当前人的上司 
        }
    }
    memset(v,true,sizeof(v));
    dfs(root);//从根节点开始搜索 
    printf("%d\n",find_max(f[root][0],f[root][1]));//输出决策是否选用第一个人(第一个人的数组已储存选与不选的最大值) 
    return 0;
}
发布了20 篇原创文章 · 获赞 1 · 访问量 6333

猜你喜欢

转载自blog.csdn.net/yichengchangan/article/details/60143789
今日推荐