两种解法-树形dp+二分+单调队列(或RMQ)-hdu-4123-Bob’s Race
题目大意:给一棵树,n个节点,每条边有个权值,从每个点i出发有个不经过自己走过的点的最远距离Ma[i],有m个询问,每个询问有个q,求最大的连续节点区间长度ans,使得该区间内最大的M[i]和最小的M[j]之差不超过q。
解题思路一:
这套题目好卡时间。
树形dp+二分+单调队列,几个基本的知识点杂糅在一起。
先用树形dp求出从任意一点i出发的Ma[i].两遍dfs,第一遍求出每个节点为根到儿子方向的最大距离并记录最大距离得到的直接儿子,和与最大距离路径没有重边的次大距离。第二遍求出每个点的最远距离Ma[i]要么从儿子方向得到,要么从父亲方向得到。
然后对于每个询问q,二分区间长度mid,用单调队列求出区间长度为mid的最大Ma[i]和最小Ma[j]的差值的最小值pp。保存起来,当已经求得了该区间长度的值pp时,直接返回pp.
与q比较,因为是存在性问题,每次都把该区间长度的最小的值pp来比较。这样一区间长度为标准,避免了,每次查询相同区间长度只是q不同的情况。不然会超时。
代码:
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<string> #include<cstring> #include<algorithm> #include<vector> #include<map> #include<set> #include<stack> #include<list> #include<queue> #include<ctime> #define eps 1e-6 #define INF 0x3f3f3f3f #define PI acos(-1.0) #define ll __int64 #define lson l,m,(rt<<1) #define rson m+1,r,(rt<<1)|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define Maxn 55000 int dp[Maxn][2],path[Maxn]; //每个点到儿子节点的最大和次大距离,最大距离经过直接儿子 int Ma[Maxn],q1[Maxn],q2[Maxn],cnt; int n,m; int re[Maxn]; struct Edge { int v,len; struct Edge * next; }*head[Maxn<<1],edge[Maxn<<1]; void add(int a,int b,int len) { ++cnt; edge[cnt].v=b,edge[cnt].len=len; edge[cnt].next=head[a]; head[a]=&edge[cnt]; } void dfs1(int cur,int pa) //第一遍dfs,求出最大距离和次大距离以及路径 { struct Edge * p=head[cur]; while(p) { int v=p->v,len=p->len; if(v!=pa) { dfs1(v,cur); if(len+dp[v][0]>dp[cur][0]) //更新最大 { dp[cur][1]=dp[cur][0];//更新次大 dp[cur][0]=len+dp[v][0]; path[cur]=v; } else if(len+dp[v][0]>dp[cur][1]) //更新次大 dp[cur][1]=len+dp[v][0]; } p=p->next; } } void dfs2(int cur,int pa,int tmp) { Ma[cur]=max(dp[cur][0],tmp);//求出最远距离,要么从儿子方向,要么从父亲方向 struct Edge * p=head[cur]; while(p) { int v=p->v,len=p->len; if(v!=pa) { if(v==path[cur])//儿子方向最大值的直接儿子,从父亲中只能选次大 dfs2(v,cur,len+max(tmp,dp[cur][1])); else dfs2(v,cur,len+max(tmp,dp[cur][0])); } p=p->next; } } int Cal(int mid)//求区间长度为mid的距离差最大 { if(re[mid]!=-1) return re[mid]; int h1=1,t1=0,h2=1,t2=0; int res=INF; for(int i=1;i<=n;i++) //用两个单调队列维护 { while(h1<=t1&&Ma[q1[t1]]<=Ma[i]) t1--; q1[++t1]=i; while(h1<=t1&&q1[h1]<=i-mid) h1++; while(h2<=t2&&Ma[q2[t2]]>=Ma[i]) t2--; q2[++t2]=i; while(h2<=t2&&q2[h2]<=i-mid) h2++; if(i>=mid) res=min(res,Ma[q1[h1]]-Ma[q2[h2]]); } re[mid]=res; //re[i]表示当区间长度为i时,Ma[i]的差值的最小值,作为最优解 return res; } int main() { while(scanf("%d%d",&n,&m)&&n+m) { cnt=0; memset(head,NULL,sizeof(head)); int a,b,c; for(int i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } memset(dp,0,sizeof(dp)); memset(re,-1,sizeof(re)); dfs1(1,1);//求出以任意点i为根儿子方向的最大和次大距离 dfs2(1,1,0); for(int i=1;i<=m;i++) { int q; scanf("%d",&q); int l=1,r=n,mid,ans=-1; //二分区间长度 while(l<=r) { mid=(l+r)>>1; if(Cal(mid)<=q) { ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); } } return 0; }
解题思路二:
求出Ma[i]数组后,可以用RMQ nlogn的时间复杂度来预处理所有区间的最大值和最小值。
然后对于每个询问q,用两个指针l,r.从前至后按以i开始能够达到最大的区间长度的顺序扫,不过当以i开始的最大的满足的区间长度为L时,向右移动l指针,此时r指针不必移动,因为现在只用考虑区间长度>=L的情况,这样就利用了只找可能满足的区间长度越来越大的情况的性质。这样每个位置最多进出一次,时间复杂度为o(N)。
所以总的时间复杂度为nlogn+m*n.
代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#include<ctime>
#define eps 1e-6
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define Maxn 55000
int dp[Maxn][2],path[Maxn]; //每个点到儿子节点的最大和次大距离,最大距离经过直接儿子
int Ma[Maxn],q1[Maxn],q2[Maxn],cnt;
int n,m;
struct Edge
{
int v,len;
struct Edge * next;
}*head[Maxn<<1],edge[Maxn<<1];
void add(int a,int b,int len)
{
++cnt;
edge[cnt].v=b,edge[cnt].len=len;
edge[cnt].next=head[a];
head[a]=&edge[cnt];
}
void dfs1(int cur,int pa) //第一遍dfs,求出最大距离和次大距离以及路径
{
struct Edge * p=head[cur];
while(p)
{
int v=p->v,len=p->len;
if(v!=pa)
{
dfs1(v,cur);
if(len+dp[v][0]>dp[cur][0]) //更新最大
{
dp[cur][1]=dp[cur][0];//更新次大
dp[cur][0]=len+dp[v][0];
path[cur]=v;
}
else if(len+dp[v][0]>dp[cur][1]) //更新次大
dp[cur][1]=len+dp[v][0];
}
p=p->next;
}
}
void dfs2(int cur,int pa,int tmp)
{
Ma[cur]=max(dp[cur][0],tmp);//求出最远距离,要么从儿子方向,要么从父亲方向
struct Edge * p=head[cur];
while(p)
{
int v=p->v,len=p->len;
if(v!=pa)
{
if(v==path[cur])//儿子方向最大值的直接儿子,从父亲中只能选次大
dfs2(v,cur,len+max(tmp,dp[cur][1]));
else
dfs2(v,cur,len+max(tmp,dp[cur][0]));
}
p=p->next;
}
}
int rmq1[20][Maxn],rmq2[20][Maxn];
int logg[Maxn];
void rmq_init()
{
for(int i=1;i<=n;i++)
rmq1[0][i]=rmq2[0][i]=Ma[i];
for(int i=1;i<=logg[n];i++) //枚举区间长度,递增补充:软件开发 , C++ ,
- 更多C/C++疑问解答:
- 关于c++的cout输出的问题。
- 在学校里学过C和C++,不过学的很一般,现在自学C#,会不会很难?
- 全国计算机二级C语言笔试题
- 已知某树有2个2度结点,3个3度结点,4个4度结点,问有几个叶子结点?
- c++数据结构内部排序问题,整数排序
- 2012九月计算机二级C语言全国题库,,急求急求
- 如果assert只有一个字符串作为参数,是什么意思呢?
- C语言中,哪些运算符具有左结合性,哪些具有右结合性,帮忙总结下,谢谢了!
- 为什么用结构体编写的程序输入是,0输不出来啊~~~
- 将IEEE—754的十六进制转化为十进制浮点类型,用C或C++都行,多谢各位大侠啊,非常感谢!
- 为什么这个程序求不出公式?
- 这个链表倒置的算法请大家分析下
- c语言函数库调用
- C语言unsigned int纠错
- C语言快排求解啊