首页 > 技术文章 > 51nod 1789 跑的比谁都快

boson-is-god 2017-01-02 20:35 原文

 
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <stack>
#define MAXN 100050
#define INF 2000000000000000000LL
#define LL long long
using namespace std;
int n, p, numEdge, top, root;
bool lef[MAXN];
int head[MAXN], star[MAXN];
LL dp[MAXN], ans;
struct Edge
{
    int v, next;
}edge[MAXN];
void addEdge(int u, int v)
{
    edge[numEdge].v = v;
    edge[numEdge].next = head[u];
    head[u] = numEdge++;
}
struct Node
{
    int id, l, r;
    Node(int I = 0, int L = 0, int R = 0): id(I), l(L), r(R){};
}S[MAXN];
LL quickPow(LL a, LL b)
{
    LL res = 1LL;
    while(b)
    {
        if(b & 1) res *= a;
        a *= a;
        b >>= 1;
    }
    return res;
}
LL work(int i, int j)
{
    return quickPow(j - i, p) + (LL)star[i];
}
void dfs(int u)
{
    stack<Node> tmp;
    int tmpR = -1;
    int lft = 1, rit = top, mid;
    if(u == root)
    {
        dp[u] = 0;
        S[++top] = Node(root, 1, n);
    }
    else
    {
        while(lft <= rit)                    //初始化dp[u]
        {
            mid = (lft + rit) >> 1;
            if(S[mid].l <= u && u <= S[mid].r)
            {
                dp[u] = dp[S[mid].id] + work(S[mid].id, u);
                break;
            }
            if(u < S[mid].l) rit = mid - 1;
            else   lft = mid + 1;
        }
        while(top > 0)                        //决策单调性的经典操作
        {
            if(dp[S[top].id] + work(S[top].id, S[top].l) < dp[u] + work(u, S[top].l))
            {
                lft = S[top].l, rit = S[top].r;
                while(lft <= rit)            //二分找出breakpoint
                {
                    mid = (lft + rit) >> 1;
                    if(dp[S[top].id] + work(S[top].id, mid) < dp[u] + work(u, mid))
                        lft = mid + 1;
                    else
                        rit = mid - 1;
                }
                tmpR = S[top].r;
                S[top].r = rit;
                break;
            }
            tmp.push(S[top]);                                //记录下从栈中撤出的元素到tmp中
            top--;
        }
        if(lft <= n) S[++top] = Node(u, lft, n);
    }
    if(head[u] == -1) ans = min(ans, dp[u]);
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        dfs(v);
    }
    if(u != root)
    {
        if(lft <= n) top--;
        if(tmpR != -1) S[top].r = tmpR;
        while(!tmp.empty())                                    //回溯的时候恢复栈
        {
            S[++top] = tmp.top();
            tmp.pop();
        }
    }
}
int main()
{
    scanf("%d%d", &n, &p);
    star[0] = numEdge = top = 0;
    for(int i = 0; i <= n; i++) head[i] = -1, lef[i] = true;
    root = 1;
    for(int fa, i = 1; i <= n; i++)
    {
        scanf("%d%d", &star[i], &fa);
        addEdge(fa, i);
        lef[fa] = false;
    }
    ans = INF;
    dfs(root);
    cout << ans << endl;
    return 0;
}
View Code

 

题目链接: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1789

一个很明显的树上dp 不过对于  维护每条链 就行了

问题在于如何优化 这是个很伤脑筋的问题,看了一个其他人的代码,还是不理解那边二分的作用是啥!QAQ

还是菜啊~

 

好了,终于明白了那大概是个什么玩意儿了! 这是一种决策性单调dp,采用的是单调栈的优化

相当于是广义的单调栈吧(口胡)

二分的作用是为了找出那个栈所含括的点,也就是栈里那个元素表示最优的最大区间(当然在程序没有结束前都只是暂时最优,也就是说右区间是可以修改的)

设那个元素为elem 那么如果elem.l >= elem.r 那么显然这个元素可以被踢出栈去了

可以二分的原因在于因为这是单调的(其实得事先证明的)

 

为什么我认为它是广义的单调栈呢?

因为我们普通的栈优化其实每次每个栈元素对应的区间是 elem.r = n的,所以不需要记录。

 

详情请见:

1D/1D动态规划优化初步

http://wenku.baidu.com/link?url=wJZ5nc-evTz4OjhPRD8yA6zZUeANcrngwYRMOAAySeCXBFvyAcrFHusd1cG3Yz8A5Ez-whuHY6nV55tx9yYLVzHJKPegne2iOZqSzqWHYbu

推荐阅读