[NOI2009]二叉查找樹
題目大意:
給定一棵嚴格的treap,父親節點的優先級必然小於兒子節點的。權值按照二叉樹的定義,左兒子小於父親小於右兒子。
深度從1開始定義,每個點除優先級、數值之外,還有一個訪問頻度。
訪問頻度所產生的代價是:訪問頻度*該點深度(這和事實相符)
可以用給定的k的代價,修改任意個點的優先級為任意實數(當然,修改優先級,樹的形態,各點深度就可能變化了)
最終的總代價為:頻度產生代價+修改代價。
最小化這個總代價。
N<=70,1<=K<=30000000
分析:
平衡樹是一個動態的數據結構,難以抓住形態的變化,也不方便記錄深度之類。所以必須抓住不變的量當做突破口。
不管平衡樹怎麽轉,根據二叉樹的定義,它的中序遍歷一定是不變的。
所以我們可以找到這棵樹的中序遍歷,就把這棵樹變成了一個靜態的區間,只不過每個區間所代表的點的優先級可能會變。
發現,每一個連續的子區間,都對應treap的連續一部分。可以把小的區間先建樹,再把大的區間用小的區間合並。我們合並的時候枚舉的劃分點,就是這部分treap的樹根
區間DP順理成章。
除了f[l][r]之外,為了維護優先級的關系,必然要再記錄一維。
發現,只要根節點的優先級確定,子樹的優先級的範圍就確定了。
所以考慮記錄根節點優先級。(這裏優先級只考慮相對大小,而且範圍又大,所以要離散化為1~n)
但是,樸素的f[l][r][w]中,w單單記錄根節點優先級的話,由於子樹所有大於w的都可以轉移,還要for一遍。狀態n^3,轉移n^2,會爆。
所以,我們令f[l][r][w]表示,將l~r這段區間建成treap,其中根節點優先級大於等於w的最小代價。
根據枚舉的根節點是否修改,可以設計轉移方程是:
修改:
f[l][r][w]=min(f[l][r][w],f[l][k-1][w]+f[k+1][r][w]+K+sum[r]-sum[l-1]) ————其中,sum[i]表示,區間中,1~i的訪問頻度和
當劃分點的優先級ch[k]大於w時,可以不修改。
f[l][r][w]=min(f[l][r][w],f[l][k-1][ch[k]]+f[k+1][r][ch[k]]+sum[r]-suim[l-1])
最後答案就是:f[1][n][1];
註意,o循環的時候,必須倒序!!因為ch[k]>=o時候,要從o更大的地方獲取最小值,必須先把o較大的處理完。
代碼1(未簡化):
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=77; const ll inf=2e18; ll f[N][N][N]; int n; ll m; int a[N]; int tot; int w[N],p[N],d[N]; int prio[N]; ll sum[N]; int ch[N];//離散化後的優先級 bool cmp(int a,int b) { return w[a]<w[b]; } int main() { scanf("%d%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&w[i]),a[i]=i; for(int i=1;i<=n;i++) scanf("%d",&p[i]),prio[i]=p[i]; for(int i=1;i<=n;i++) scanf("%d",&d[i]); sort(a+1,a+n+1,cmp); sort(prio+1,prio+n+1); for(int i=1;i<=n;i++) ch[i]=lower_bound(prio+1,prio+n+1,p[a[i]])-prio;//離散化 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) f[i][j][k]=inf; for(int i=1;i<=n;i++) for(int k=n;k>=1;k--) { if(k<=ch[i]) f[i][i][k]=d[a[i]]; else f[i][i][k]=d[a[i]]+m;//註意,k>ch的時候,不一定是+oo,可以通過修改改變 }//l=1的初值 for(int i=1;i<=n;i++) sum[i]=sum[i-1]+d[a[i]];//前綴和 for(int l=2;l<=n;l++) for(int i=1;i<=n;i++) { int j=l+i-1; if(j>n) break; if(l==2)//長度為二的時候,只能二並一 { for(int o=n;o>=1;o--) { f[i][j][o]=min(f[i][j][o],f[i][i][o]+m+sum[j]-sum[i-1]); f[i][j][o]=min(f[i][j][o],f[j][j][o]+m+sum[j]-sum[i-1]); if(ch[i]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[i]]+f[j][j][ch[i]]+sum[j]-sum[i-1]-d[a[i]]); if(ch[j]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[j]]+f[j][j][ch[j]]+sum[j]-sum[i-1]-d[a[j]]); } } else{ for(int o=n;o>=1;o--) for(int k=i;k<=j;k++) { if(k==i)//k在端點處,只能用端點和右邊所有部分合並 { f[i][j][o]=min(f[i][j][o],f[i+1][j][o]+m+sum[j]-sum[i-1]); if(ch[i]>=o) f[i][j][o]=min(f[i][j][o],f[i][i][ch[i]]+f[i+1][j][ch[i]]+sum[j]-sum[i-1]-d[a[i]]); } else if(k==j)//同理 { f[i][j][o]=min(f[i][j][o],f[i][j-1][o]+m+sum[j]-sum[i-1]); if(ch[j]>=o) f[i][j][o]=min(f[i][j][o],f[i][j-1][ch[j]]+f[j][j][ch[j]]+sum[j]-sum[i-1]-d[a[j]]); } else{//正宗轉移方程 f[i][j][o]=min(f[i][j][o],f[i][k-1][o]+f[k+1][j][o]+m+sum[j]-sum[i-1]); if(ch[k]>=o) f[i][j][o]=min(f[i][j][o],f[i][k-1][ch[k]]+f[k+1][j][ch[k]]+sum[j]-sum[i-1]); } } } } printf("%lld",f[1][n][1]); return 0; }
太惡心了。為了保證l<=r,做出了巨大的討論。
其實不用這麽麻煩,只要讓l>r的時候,賦值為0就好,相當於不存在。根本不影響答案。
代碼2(化簡)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=77; const ll inf=2e18; ll f[N][N][N]; int n; ll m; int a[N]; int tot; int w[N],p[N],d[N]; int prio[N]; ll sum[N]; int ch[N];//離散化後的優先級 bool cmp(int a,int b) { return w[a]<w[b]; } int main() { scanf("%d%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&w[i]),a[i]=i; for(int i=1;i<=n;i++) scanf("%d",&p[i]),prio[i]=p[i]; for(int i=1;i<=n;i++) scanf("%d",&d[i]); sort(a+1,a+n+1,cmp); sort(prio+1,prio+n+1); for(int i=1;i<=n;i++) ch[i]=lower_bound(prio+1,prio+n+1,p[a[i]])-prio; for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) for(int o=0;o<=n;o++) f[i][j][o]=inf; for(int i=1;i<=n;i++) for(int o=0;o<=n;o++) f[i][i-1][o]=0;//其實這步不需要,因為上面就沒有給它賦值,只是在這裏強調一下。 for(int i=1;i<=n;i++) sum[i]=sum[i-1]+d[a[i]]; for(int o=n;o>=1;o--) for(int i=n;i>=1;i--) for(int j=i;j<=n;j++) for(int k=i;k<=j;k++) { f[i][j][o]=min(f[i][j][o],f[i][k-1][o]+f[k+1][j][o]+m+sum[j]-sum[i-1]); if(ch[k]>=o) f[i][j][o]=min(f[i][j][o],f[i][k-1][ch[k]]+f[k+1][j][ch[k]]+sum[j]-sum[i-1]); }//不放心,可以考慮代入長度小於等於2的情況。0的作用就出來了。 //連初始化l=1都省了。 printf("%lld",f[1][n][1]); return 0; }
總結:
1.對於琢磨不透的變化,一定有不變的東西。一定要抓住其中的不變量,作為突破口。
2.循環順序要註意,一個是不能有後效性,一個是要保證能影響到這個狀態的所有狀態都處理完了。
3.註意考慮清楚所有可能轉移的方式。
[NOI2009]二叉查找樹