POJ - 3162 Walking Race 树形dp 单调队列

POJ - 3162Walking Race

   题目大意:有n个训练点,第i天就选择第i个训练点为起点跑到最远距离的点,然后连续的几天里如果最远距离的最大值和最小值的差距不超过m就可以作为观测区间,问这样的区间最长的长度?

  一开始楞是没看懂题意,最讨厌这种四级题,这是在刁难我英语小能手(能用翻译的就不自己动手)。而且这题感觉单调队列那里的处理更难一点,不过还是来说一说怎么树形dp取得最远距离,先画个简简单单丑丑的图

  我们直接从1作为根节点开始dfs的话,可以处理1的最远距离,并且可以得出到其它节点在其子树中的最短距离,然后怎么得到其他节点的最远距离呢?我们还是从1开始,2的最短距离无非就是1和2连接这一条边再加上除这条外的最长分支也就是1和4这条边,而3的话1和3连接的这条边是在1和4这条路径上的,所以对3来说最远距离就是1的除1和4这条外其他最长分支加上1和3连接的这条边与3和4连接的这条边中的最大值,所以我们可以更新一个每个节点的最大距离以及第二远距离还有最远距离来自哪个子节点的方向。

  这样的话先一遍dfs得出作为根节点的情况,还有其他节点到子树的情况,然后第二边dfs再由根节点去传入数据更新其他节点的最大值,也就判断子节点是否是最远距离方向的,是的话传入第二远距离,否则传入最远距离。

  然后就是取区间那里了,首先尺取思想,但需要一个单调递减队列维护区间最大值(最大值在队列首部),然后还需要一个单调递增队列维护区间最小值(最小值在队列首部),然后区间往右的同时,更新左端,详情见代码。

  1 #include<cstdio>
  2 #include<queue>
  3 #include<algorithm>
  4 using namespace std;
  5 const int N=1000118;
  6 struct Side{
  7     int v,ne,len;
  8 }S[2*N];
  9 int sn,n,m,head[N],dis1[N],dis2[N],path[N];
 10 void add(int u,int v,int c)
 11 {
 12     S[sn].v=v;
 13     S[sn].len=c;
 14     S[sn].ne=head[u];
 15     head[u]=sn++;
 16 }
 17 void updata(int u,int v,int dis)//更新距离 
 18 {
 19     if(dis>dis1[u])//更新最大距离的来源点 
 20         path[u]=v;
 21     dis2[u]=max(dis2[u],min(dis1[u],dis)); 
 22     dis1[u]=max(dis1[u],dis);
 23 }
 24 void dfs1(int u,int f)
 25 {
 26     for(int i=head[u];i!=-1;i=S[i].ne)
 27     {
 28         int v=S[i].v;
 29         if(v!=f)
 30         {
 31             dfs1(v,u);//先搜索子节点 
 32             updata(u,v,dis1[v]+S[i].len);//更新 
 33         }
 34     }
 35 }
 36 void dfs2(int u,int f,int len)
 37 {
 38     updata(u,f,len);//由父节点传入的距离更新距离 
 39     for(int i=head[u];i!=-1;i=S[i].ne)
 40     {
 41         int v=S[i].v;
 42         if(v!=f)
 43         {
 44             if(v==path[u])
 45                 dfs2(v,u,dis2[u]+S[i].len);
 46             else
 47                 dfs2(v,u,dis1[u]+S[i].len);
 48         }
 49     }
 50 }
 51 int main()
 52 {
 53     int f,d;
 54     while(~scanf("%d%d",&n,&m))
 55     {
 56         for(int i=0;i<=n;i++)
 57         {
 58             head[i]=-1;
 59             dis1[i]=dis2[i]=0;
 60         }
 61         sn=0;
 62         for(int i=1;i<n;i++)
 63         {
 64             scanf("%d%d",&f,&d);
 65             add(i+1,f,d);
 66             add(f,i+1,d);
 67         }
 68         dfs1(1,0);
 69         dfs2(1,0,0);
 70         deque<int> qx,qd;//qx单调递增队列,qd单调递减队列  
 71         int ans=0;         //注意是双向队列,单向没有back() 
 72         for(int i=1,j=0;i<=n;i++)//j是左端,i是右端 
 73         {
 74             //维护单调性 
 75             while(qd.size()&&dis1[qd.back()]<dis1[i])
 76                 qd.pop_back();
 77             while(qx.size()&&dis1[qx.back()]>dis1[i])
 78                 qx.pop_back();
 79             qd.push_back(i);
 80             qx.push_back(i);
 81             while(qd.size()&&qx.size()&&dis1[qd.front()]-dis1[qx.front()]>m)
 82             {
 83                 //当最大值减最小值大于m时这时左端的位置就需要调整了
 84                 //左端肯定是要调整到最大值和最小值中较近的位置的前面 
 85                 if(qd.front()<qx.front())
 86                 {
 87                     j=qd.front();
 88                     qd.pop_front();
 89                 }
 90                 else
 91                 {
 92                     j=qx.front();
 93                     qx.pop_front();
 94                 }
 95                 //把区间外的数全部弹出 
 96                 while(qd.size()&&qd.front()<=j)
 97                     qd.pop_front();
 98                 while(qx.size()&&qd.front()<=j)
 99                     qx.pop_front();
100             }
101             ans=max(ans,i-j);//更新区间长度 
102         }
103         printf("%d\n",ans);
104     }
105     return 0;
106 }
简简单单的低调

  连做了几题树形dp,不怎么说得出,不过感觉还是有点心得的。核心的思想还是在于它的一棵数的结构,我们可以把某个节点假设为根节点再由它的情况去推导出整棵树的情况。而且精华部分也正如dp,搞清楚dp什么,然后是状态转移的过程。想练好它,无疑就两个方法,做题,思考。

转载于:https://www.cnblogs.com/LMCC1108/p/10534663.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值