POJ 3162 Walking Race(树形DP+单调队列)

273 篇文章 0 订阅
112 篇文章 0 订阅

POJ 3162 Walking Race(树形DP+单调队列)

分析:首先这道题和HDU4123很像,不过这道题目数据规模很大,空间有限,不能用RMQ求解了,要用单调队列进行第二步处理。

HDU4123http://blog.csdn.net/u013480600/article/details/21834553

      依然先树形DP,求出所有的d[i]距离,然后对于m找出最长的连续区间。维护两个单调队列qmin[n]qmax[n], qminqmax中存的都是元素的下标.然后每次同时往qminqmax尾部加上d[j],使得qmin中的元素按照d值增摆放, qmax中的元素按照d值减摆放.那么qmin中的第一个元素就是当前区间[i , j]内的最小值的下标,qmax中的第一个元素就是当前区间[i , j]内的最大值下标.

初始时i 1,j1.然后当添加了某个元素后使得d[qmax[front2]]-d[qmin[front1]] >M的话,说明我们刚添加的元素不符合要求,但是j-1这个元素还是符合要求的所以[i , j]内的ans=j-i.

现在问题是下一步我们应该找那些区间呢?i+1作为左区间开始还是?因为我们刚添加的元素d[j]使得:

d[qmax[front2]]-d[qmin[front1]] >M,所以如果令i=min(qmax[front2],qmin[front1]),那么依然d[qmax[front2]]-d[qmin[front1]] >M,不合法,且这时候得到的j-i更小没有意义.

所以i必须从min(qmax[front2],qmin[front1])+1开始,这样就有可能使得最大值减最小值合法,则可以更新ans的值.

      上面的理论其实也是让左区间i保持不变,j最大能到什么位置.更新ans,然后i到下一个合法的位置上去继续尽量找到最大j.

AC代码:3469ms

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN =1000000+5;
int tot;
int longest[MAXN];
int dist[MAXN][3];
int d[MAXN];
int head[MAXN];
int n,m;
int qmin[MAXN],qmax[MAXN];
struct edge
{
    int to;
    int next;
    int w;
}edges[MAXN*2];
void add_edge(int u,int v,int w)
{
    edges[tot].to=v;
    edges[tot].w=w;
    edges[tot].next=head[u];
    head[u]=tot++;
}
int dfs1(int u,int fa)
{
    if(dist[u][0]>=0)return dist[u][0];
    dist[u][0]=dist[u][1]=dist[u][2]=longest[u]=0;
    for(int e=head[u];e!=-1;e=edges[e].next)
    {
        int v= edges[e].to;
        if(v==fa)continue;

        if(dist[u][0]<dfs1(v,u)+edges[e].w)
        {
            dist[u][1] = dist[u][0];
            longest[u]=v;
            dist[u][0]=dfs1(v,u)+edges[e].w;
        }
        else if(dist[u][1]<dfs1(v,u)+edges[e].w)
            dist[u][1] = dfs1(v,u)+edges[e].w;
    }
    return dist[u][0];
}
void dfs2(int u,int fa)
{
    for(int e=head[u];e!=-1;e=edges[e].next)
    {
        int v=edges[e].to;
        if(v==fa)continue;

        if(v==longest[u])dist[v][2]=max(dist[u][2],dist[u][1])+edges[e].w;
        else dist[v][2]=max(dist[u][2],dist[u][0])+edges[e].w;
        dfs2(v,u);
    }
}
void solve()
{
    int ans=0;
    int i,j,front1,front2,rear1,rear2;
    front1=rear1=front2=rear2=0;//front1是队首元素的位置,rear1是对尾元素的下一个位置
    for(i=j=1;j<=n;j++)
    {
        while(front1<rear1 && d[qmin[rear1-1]]>=d[j])rear1--;
        qmin[rear1++]=j;//qmin中存的是d[j]的下标,且qmin中按值增存
        while(front2<rear2 && d[qmax[rear2-1]]<=d[j])rear2--;
        qmax[rear2++]=j;//qmax中存的是d[j]的下标,且qmax中按值减存
        //这样qmin中始终保持队首元素是区间[l,r]内d值最小的,qmax保证队首位d值最大的
        if(d[qmax[front2]]-d[qmin[front1]]>m)//突然不合法
        {
            ans = max(ans,j-i);
            i=min(qmin[front1],qmax[front2])+1;//此步的i必然使得最大值-最小值<=m,否则就是i已经>n了
            while(front1<rear1 && qmin[front1]<i)front1++;
            while(front2<rear2 && qmax[front2]<i)front2++;
        }
    }
    ans = max(ans,j-i);
    printf("%d\n",ans);
}

int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        if(n==0&&m==0)break;
        tot=0;
        memset(dist,-1,sizeof(dist));
        memset(head,-1,sizeof(head));
        for(int i=2;i<=n;i++)
        {
            int v,w;
            scanf("%d%d",&v,&w);
            add_edge(i,v,w);
            add_edge(v,i,w);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++)
            d[i] = max(dist[i][0] , dist[i][2]);
        solve();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值