两种解法-树形dp+二分+单调队列(或RMQ)-hdu-4123-Bob’s Race
題目鏈接:
http://acm.hdu.edu.cn/showproblem.php?pid=4123
題目大意:
給一棵樹,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 55000int 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]));elsedfs2(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;}elser=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 55000int 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]));elsedfs2(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++) //枚舉區間長度,遞增{for(int j=1;j<=n;j++){rmq1[i][j]=max(rmq1[i-1][j],rmq1[i-1][j+(1<<(i-1))]);rmq2[i][j]=min(rmq2[i-1][j],rmq2[i-1][j+(1<<(i-1))]);}} } int rmq_min(int l,int r) {int tmp=logg[r-l+1];int a=max(rmq1[tmp][l],rmq1[tmp][r-(1<<tmp)+1]);int b=min(rmq2[tmp][l],rmq2[tmp][r-(1<<tmp)+1]);return a-b; } int main() {logg[0]=-1;for(int i=1;i<=50000;i++)logg[i]=logg[i>>1]+1; //logg[i]表示int logg2(i)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));dfs1(1,1);//求出以任意點i為根兒子方向的最大和次大距離dfs2(1,1,0);rmq_init();for(int i=1;i<=m;i++){int q;scanf("%d",&q);int l=1,r=1,ans=0;while(r<=n&&l<=n){if(rmq_min(l,r)<=q) //找到以l開始的能滿足的最大區間長度{ans=max(ans,r-l+1);r++;}else //以i+1點開始的區間長度必須大于等于前面的滿足區間長度才可以,所以r指針可以不動l++;}printf("%d\n",ans);}}return 0; }?
?
轉載于:https://www.cnblogs.com/james1207/p/3339593.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的两种解法-树形dp+二分+单调队列(或RMQ)-hdu-4123-Bob’s Race的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《MacTalk·人生元编程》
- 下一篇: 打算自己做app,你们做过吗?