区间dp入门

题目:
洛谷P1040加分二叉树
大意是给一个正整数序列,它是一棵二叉树的中序遍历结果;一棵树的加分定义为左子树加分x右子树加分+根的值,若某个子树为空,则它的加分是1。
要求输出这棵树的最大加分,和前序遍历结果。
样例输入:
5
5 7 1 2 10
输出:
145
3 1 2 4 5

首先得明白题,对于一个中序遍历序列,二叉树的形态有很多种可能,题目要求找出加分最大的那种形态。对于二叉树,最重要的是确定树根,中序序列[1,n]的树根可能有n种,分别是(1,2,3...n),枚举每一个可能的树根,递归算出左右子树最大加分,最后将加分最大的定为树根。

一、暴力搜索

#include <cstdio>
#define MAXN 30
typedef long long ll;
using namespace std;

rs表示 区间[i,j]当加分最大时,树的根
int n, a[MAXN+1], rs[MAXN][MAXN];
//搜索从l到r这个区间内的最大加分,并确定根节点
ll dfs(int l, int r){
    if (r == l - 1){//这是空树
        return 1;
    } else if (l == r){//叶子
        return a[l];
    } else if (r == l + 1){//序列只有两个数,由于是中序,根必然是l
        rs[l][r] = l;
        return a[l] + a[r];
    } else {//枚举每一个数,使其成为根节点,
        ll max = -1;
        for(int root = l; root <= r; root++){
            ll ans = dfs(l, root - 1) * dfs(root + 1, r) + a[root];
            if (ans > max){
                rs[l][r] = root;
                max = ans;
            }
        }
        return max;
    }
}
void print(int l, int r){//前序遍历输出
    if (l > r){//空树,什么都不输出
        return;
    } else if (l == r){//叶子,直接输出
        printf("%d ", l);
        return;
    }
    int root = rs[l][r];
    printf("%d ", root);//先输出根
    print(l, root - 1);//输出左子树
    print(root + 1, r);//输出右子树
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", a + i);
    }
    ll ans = dfs(1, n);
    printf("%lld\n", ans);
    print(1, n);

    return 0;
}

二、记忆化,暴力搜索有很多重复计算,比如样例5个数的序列,1为根,[2,5]为右子树时,需要计算[3,5];2为根时,也需要计算[3,5],很慢,加上记忆化就快了,把算过的存起来,下次直接用。

#include <cstdio>
#define MAXN 30
typedef long long ll;
using namespace std;

int n, a[MAXN+1], rs[MAXN][MAXN];
ll f[MAXN][MAXN];//记忆化数组
ll dfs(int l, int r){
    if (r == l - 1){
        return 1;
    } else if (l == r){
        return a[l];
    }
    if (f[l][r]){
        return f[l][r];
    }
    if (r == l + 1){
        rs[l][r] = l;
        return f[l][r] = a[l] + a[r];
    } else {
        ll max = -1;
        for(int root = l; root <= r; root++){
            ll ans = dfs(l, root - 1) * dfs(root + 1, r) + a[root];
            if (ans > max){
                rs[l][r] = root;
                max = ans;
            }
        }
        return f[l][r] = max;
    }
}
void print(int l, int r){
    if (l > r){
        return;
    } else if (l == r){
        printf("%d ", l);
        return;
    }
    int root = rs[l][r];
    printf("%d ", root);
    print(l, root - 1);
    print(root + 1, r);
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", a + i);
    }
    ll ans = dfs(1, n);
    printf("%lld\n", ans);
    print(1, n);

    return 0;
}

三、区间dp

记忆化搜索非常好,但此题对于学习区间dp很有帮助,必须要掌握。
根据记忆化搜索方法,状态和转移方程已经浮现出来,f[i][j]表示中序序列从i到j的最大加分,转移方程为:
f[i][j] = max(f[i][k-1] * f[k+1][j] + a[k]), k属于[i,j]
最终结果就是f[1][n]
dp一般自底向上求解,先解决小问题,然后逐步放大,显然f[i][i]是叶子结点,它的加分是已知的,即f[i][i]=a[i],f[i][i-1]表示空树,加分是1,把求解区间长度逐渐放大不断扩大规模,即枚举区间长度,从1到n,最后算出f[1][n]。

#include <cstdio>
#define MAXN 30
typedef long long ll;
using namespace std;

int n, rs[MAXN+1][MAXN+1];
ll f[MAXN+1][MAXN+1];
void print(int l, int r){
    if (l > r){
        return;
    } else if (l == r){
        printf("%d ", l);
        return;
    }
    int root = rs[l][r];
    printf("%d ", root);
    print(l, root - 1);
    print(root + 1, r);
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &f[i][i]);
        f[i][i-1] = 1;
        rs[i][i] = i;
    }
    for(int i = 1; i < n; i++){
        f[i][i+1] = f[i][i] + f[i+1][i+1];
        rs[i][i+1] = i;
    }
    for(int len = 3; len <= n; len++){
        for(int i = 1; ; i++){
            if (i + len - 1 > n) break;
            //枚举每一个根,根从i到i+len-1
            for(int j = 0; j < len; j++){
                int r = i + j;
                ll v = f[i][r-1] * f[r+1][i+len-1] + f[r][r];
                if (f[i][i+len-1] < v){
                    f[i][i+len-1] = v;
                    rs[i][i+len-1] = r;
                }
            }
        }
    }
    printf("%lld\n", f[1][n]);
    print(1, n);

    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容