兩種解法-樹形dp+二分+單調佇列(或RMQ)-hdu-4123-Bob’s Race
阿新 • • 發佈:2019-02-15
題目連結:
題目大意:
給一棵樹,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++) //列舉區間長度,遞增 { 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; }