幸福的道路_bzoj2500_树形dp+单调队列

Description


小T与小L终于决定走在一起,他们不想浪费在一起的每一分每一秒,所以他们决定每天早上一同晨练来享受在一起的时光.
他们画出了晨练路线的草图,眼尖的小T发现可以用树来描绘这个草图.
他们不愿枯燥的每天从同一个地方开始他们的锻炼,所以他们准备给起点标号后顺序地从每个起点开始(第一天从起点一开始,第二天从起点二开始……). 而且他们给每条道路定上一个幸福的值.很显然他们每次出发都想走幸福值和最长的路线.
他们不愿再经历之前的大起大落,所以决定连续几天的幸福值波动不能超过M.他们想知道要是这样的话他们最多能连续锻炼多少天(hint:不一定从第一天一直开始连续锻炼)?
现在,他们把这个艰巨的任务交给你了!

Input


第一行包含两个整数N, M(M<=10^9).
第二至第N行,每行两个数字Fi , Di, 第i行表示第i个节点的父亲是Fi,且道路的幸福值是Di.

Output


最长的连续锻炼天数

数据范围:


50%的数据N<=1000
80%的数据N<=100000
100%的数据N<=1000000

Analysis


先考虑树形dp,设f[i]表示节点i向下能走的最长路,g[i]表示节点i向上再向下能走的最长路
由于不能走重复的路,我们发现这样显然是不行的(向上再向下会走重复的某一段)。于是改一下f[i][0]f[i][1]分别表示节点i向下走的最长路和次长路,g[i][0]g[i][1]同理,用两个dfs转移状态

为了找到连续的最长一段极值差不超过m的序列,解决方法是分别开单调递减和单调递增的两个队列就可以了

Code


/*
ID:wjp13241
PROG:myp
LANG:C++
*/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <iostream>
#include <vector>
#include <cmath>
#include <stack>
#include <queue>
#include <map>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fore(i,x,e) for(int i=ls[x];i;i=e[i].next)
#define fil(x,t) memset(x,t,sizeof(x))
#define max(x,y) x>y?x:y
#define INF 0x3f3f3f3f
#define N 1000005
#define E N*2+1
using namespace std;
struct edge{int y,w,next;}e[E];
int d[N][2],r[N][2],ls[N],q[N],t[N],maxE=0;
int add(int x,int y,int w){e[++maxE]=(edge){y,w,ls[x]};ls[x]=maxE;}
inline int read(){
    int x=0,v=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')v=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*v;
}
inline int dfsD(int fa,int x)
{
    fore(i,x,e)
        if (e[i].y!=fa)
        {
            int y=e[i].y,w=e[i].w;
            dfsD(x,y);
            if (d[y][0]+w>d[x][0])
            {
                d[x][1]=d[x][0];
                r[x][1]=r[x][0];
                d[x][0]=d[y][0]+w;
                r[x][0]=y;
            }
            else
                if (d[y][0]+w>d[x][1])
                {
                    d[x][1]=d[y][0]+w;
                    r[x][1]=y;
                }
        }
}
inline int dfsF(int fa,int x,int l)
{
    int w=l+d[fa][0];
    if (r[fa][0]==x)
        w=l+d[fa][1];
    if (w>d[x][0])
    {
        d[x][1]=d[x][0];
        r[x][1]=r[x][0];
        d[x][0]=w;
        r[x][0]=fa;
    }
    else
        if (w>d[x][1])
        {
            d[x][1]=w;
            r[x][1]=fa;
        }
    fore(i,x,e)
        if (e[i].y!=fa)
            dfsF(x,e[i].y,e[i].w);
}
int main()
{
    int n=read(),m=read();
    fo(i,2,n)
    {
        int x=read(),w=read();
        add(x,i,w);
        add(i,x,w);
    }
    dfsD(0,1);
    dfsF(0,1,0);
    int qh=1,qt=0,th=1,tt=0,ans=0,id=0;
    fo(i,1,n)
    {
        while (qh<=qt&&d[i][0]<=d[q[qt]][0])
            qt--;
        q[++qt]=i;
        while (th<=tt&&d[i][0]>=d[t[tt]][0])
            tt--;
        t[++tt]=i;
        while (d[t[th]][0]-d[q[qh]][0]>m)
            if (q[qh]<t[th])
                id=q[qh++];
            else
                id=t[th++];
        ans=max(ans,i-id);
    }
    printf("%d\n",ans);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值