NOI2009 二叉查找樹 【區間dp】
阿新 • • 發佈:2017-07-27
pmod 解決 cst sum getc rep 必須 中序遍歷 結點 樹中的訪問代價為它的訪問頻度乘以它在樹中的深度。整棵樹的訪問代價定義為
所有結點在樹中的訪問代價之和。
現在給定每個結點的數據值、 權值和訪問頻度, 你可以根據需要修改某些
結點的權值,但每次修改你會付出 K 的額外修改代價。你可以把結點的權值改
為任何實數,但是修改後所有結點的權值必須仍保持互不相同。現在你要解決的
問題是, 整棵樹的訪問代價與額外修改代價的和最小是多少?
【輸入格式】
輸入文件為 treapmod.in。
輸入文件第一行包含兩個正整數 N 和 K。 N 為結點的個數, K 為每次
修改所需的額外修改代價。
接下來一行包含 N 個非負整數,是每個結點的數據值。
再接下來一行包含 N 個非負整數,是每個結點的權值。
再接下來一行包含 N 個非負整數,是每個結點的訪問頻度。
所有的數據值、權值、訪問頻度均不超過 400000。每兩個數之間都有一個
空格分隔,且行尾沒有空格。
【輸出格式】
輸出文件為 treapmod.out。
輸出文件只有一個數字,即你所能得到的整棵樹的訪問代價與額外修改代價
之和的最小值。
【樣例 1 輸入】
4 10
1 2 3 4
1 2 3 4
1 2 3 4
【樣例 1 輸出】
29
第 6 頁 共 8 頁
【樣例 1 說明】
輸入的原圖是左圖,它的訪問代價是 1×1+2×2+3×3+4×4=30。最佳的修改方
案 是 把 輸 入 中 的 第 3 個 結 點 的 權 值 改 成 0 , 得 到 右 圖 ,
訪 問 代 價 是 1×2+2×3+3×1+4×2=19,加上額外修改代價 10,一共是 29。
【子任務】
40%的數據滿足 N ≤ 30;
70%的數據滿足 N ≤ 50;
100%的數據滿足 N ≤ 70, 1 ≤ K ≤ 30000000。
【NOI2009】二叉查找樹
【問題描述】
已知一棵特殊的二叉查找樹。根據定義,該二叉查找樹中每個結點的數據值
都比它左子樹結點的數據值大,而比它右子樹結點的數據值小。
另一方面,這棵查找樹中每個結點都有一個權值,每個結點的權值都比它的
兒子結點的權值要小。
已知樹中所有結點的數據值各不相同;所有結點的權值也各不相同。這時可
得出這樣一個有趣的結論:如果能夠確定樹中每個結點的數據值和權值,那麽樹
的形態便可以唯一確定。因為這樣的一棵樹可以看成是按照權值從小到大順序插
入結點所得到的、按照數據值排序的二叉查找樹。
一個結點在樹中的深度定義為它到樹根的距離加 1。因此樹的根結點的深度
為 1。
每個結點除了數據值和權值以外,還有一個訪問頻度。我們定義一個結點在
所有結點在樹中的訪問代價之和。
現在給定每個結點的數據值、 權值和訪問頻度, 你可以根據需要修改某些
結點的權值,但每次修改你會付出 K 的額外修改代價。你可以把結點的權值改
為任何實數,但是修改後所有結點的權值必須仍保持互不相同。現在你要解決的
問題是, 整棵樹的訪問代價與額外修改代價的和最小是多少?
【輸入格式】
輸入文件為 treapmod.in。
輸入文件第一行包含兩個正整數 N 和 K。 N 為結點的個數, K 為每次
修改所需的額外修改代價。
接下來一行包含 N 個非負整數,是每個結點的數據值。
再接下來一行包含 N 個非負整數,是每個結點的權值。
所有的數據值、權值、訪問頻度均不超過 400000。每兩個數之間都有一個
空格分隔,且行尾沒有空格。
【輸出格式】
輸出文件為 treapmod.out。
輸出文件只有一個數字,即你所能得到的整棵樹的訪問代價與額外修改代價
之和的最小值。
【樣例 1 輸入】
4 10
1 2 3 4
1 2 3 4
1 2 3 4
【樣例 1 輸出】
29
第 6 頁 共 8 頁
【樣例 1 說明】
輸入的原圖是左圖,它的訪問代價是 1×1+2×2+3×3+4×4=30。最佳的修改方
案 是 把 輸 入 中 的 第 3 個 結 點 的 權 值 改 成 0 , 得 到 右 圖 ,
【子任務】
40%的數據滿足 N ≤ 30;
70%的數據滿足 N ≤ 50;
100%的數據滿足 N ≤ 70, 1 ≤ K ≤ 30000000。
【題解】
數據值已經確定了,那麽這棵樹的中序遍歷就已經確定了,我們相當於改變權值來改變深度。
而權值可以是任意實數,但是卻和答案無關,我們完全可以裏離散化一下。
那麽我們設dp[i][j][w]表示以區間[i,j]內的節點為樹,區間內的點都>=w的方案總數。
我們通過枚舉區間內的根k,可以寫出轉移方程(sum(i,j)表示[i,j]的訪問頻率和)。
當k>=w時,我們就可以把k之間弄成根,dp[i][j][w]=min(dp[i][k-1][wk]+dp[k+1][j][wk]+sum(i,j));
也可以把k的權值修改成w,dp[i][j][w]=min(dp[i][k-1][w]+dp[k+1][w]+K+sum(i,j));
因為如果之間dp偏序關系不太明確,在實現時可以使用記憶化搜索。(表示一開始直接dp調了一個上午沒調出來)
【Code】
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> using namespace std; #define ll long long #define REP(i,a,b) for(register int i=(a),_end_=(b);i<=_end_;i++) #define DREP(i,a,b) for(register int i=(a),_end_=(b);i>=_end_;i--) #define EREP(i,a) for(register int i=start[(a)];i;i=e[i].next) inline int read() { int sum=0,p=1;char ch=getchar(); while(!((‘0‘<=ch && ch<=‘9‘) || ch==‘-‘))ch=getchar(); if(ch==‘-‘)p=-1,ch=getchar(); while(‘0‘<=ch && ch<=‘9‘)sum=sum*10+ch-48,ch=getchar(); return sum*p; } const int maxn=100; int n,K; struct node { int a,b,c; }; node g[maxn]; bool cmp (const node x,const node y) { return x.a<y.a; } int Sum(int x,int y) { return g[y].c-g[x-1].c; } void init() { int s[maxn]; n=read();K=read(); REP(i,1,n)g[i].a=read(); REP(i,1,n)g[i].b=read(),s[i]=g[i].b; REP(i,1,n)g[i].c=read(); sort(g+1,g+n+1,cmp); sort(s+1,s+n+1); int cnt=unique(s+1,s+n+1)-(s+1); REP(i,1,n) { REP(j,1,cnt) { if(g[i].b==s[j]){g[i].b=j;break;} } } REP(i,1,n)g[i].c+=g[i-1].c; } ll dp[maxn][maxn][maxn]; void chkmin(ll &a,ll b) { if(a>b)a=b; } #define inf 0x7ffffffffff ll dfs(int l,int r,int k) { if(l>r)return 0; if(~dp[l][r][k])return dp[l][r][k]; ll ans=inf; REP(i,l,r) { chkmin(ans,dfs(l,i-1,k)+dfs(i+1,r,k)+K); if(g[i].b>=k)chkmin(ans,dfs(l,i-1,g[i].b)+dfs(i+1,r,g[i].b)); } return dp[l][r][k]=ans+Sum(l,r); } void doing() { memset(dp,-1,sizeof(dp)); //REP(i,1,n)dp[i][i][g[i].b]=Sum(i,i); /*REP(len,1,n-1) { REP(i,1,n-len) { int j=i+len; REP(k,1,Min(i,j)) { REP(l,i,j) { int Mn=Min(i,j),z=0; if(Mn==g[l].b)z=0;else z=K; chkmin(dp[i][j][k],dp[i][l-1][Mn]+dp[l+1][j][Mn]+z+Sum(i,j)); } } } }*/ cout<<dfs(1,n,1)<<endl; } int main() { init(); doing(); return 0; }
NOI2009 二叉查找樹 【區間dp】