hdu 1055(贪心)
思路:尋找最大權值,合并這個節點和他的父親節點,記下這兩個節點的拓撲序列,同時新節點的權值為這些節點的算術平均值,直到只有一個節點。因為這個節點必定是訪問該節點的父節點之后第一個訪問的節點。
?證明:
對于每一次新的訪問,我們要計算所有未被訪問的點權之和。顯然這個計算式很繁瑣且不易處理。
ò??思考:根據訪問代價的計算規則,對于根節點,它的權值需要計算1次;對于第2個訪問的點,它的權值需要計算2次;對于第3個點……以此類推。
ò??發現:每個點權的計算次數只與遍歷順序有關!即訪問這個點的時間戳。
?
ò??給定一棵N個有權值的節點的有根樹(默認根節點編號為1)。每個節點的權值為Ci。
ò??現在需要遍歷這棵樹。每個點的訪問代價為這個點的權值與訪問它的時間戳的乘積。
ò??遍歷只能按拓撲序,即訪問i時i的父親必須已經被訪問。
ò??求最小遍歷代價。遍歷代價為每個點的訪問代價之和。
ò??轉化的好處?使代價函數的計算式更加確定且易于計算。
?
ò??定義訪問序列為一個排列P={i1,i2,i3...in-1,in},表示節點的訪問順序。
ò??定義點權序列為T={Ci1,Ci2,Ci3…Cin-1,Cin}。
ò??由簡化問題的貪心解法,我們考慮盡量訪問當前未訪問的點權最大節點i。然而由于拓撲序的限制,我們想要訪問它,必須先訪問它的父親。
ò??猜想:當前點權最大的節點i必定是在訪問它的父親j后立刻訪問,否則得不到最優答案。
ò??猜想是否正確?
?
?????令當前最大節點為i,它的父親為j。在訪問序列P中i的位置為xi,j的位置為xj,假設xi+1<xj。
ò??關鍵:對于所有的k滿足xj<xk<xi,這些點必定不是j的祖先或i的后代。
ò??我們在序列P中交換i和k,P仍然是一個合法的訪問序列。
ò??顯然地,由于Ci是訪問j時所有未訪問點的點權最大值,那么交換后的訪問序列P對應的遍歷代價變小了。
ò??進一步,將i交換到xj+1的位置是最優的。
ò??于是猜想是正確的。
ò??結論1:當前最大節點Ci必定是在訪問它的父親j后立刻訪問。
ò???
ò??由結論1知,在訪問序列中,i和j應該是相鄰的,j在前,i在后。
ò??那么i和j可以合并為一個結點k,k的父節點與j的父結點相同,k的子結點是所有j的子結點和i的子結點。然后用k代替樹中的j和i,這樣形成一棵n-1個結點的樹。
ò??合并的好處?
ò??問題的規模縮小了。
ò??新的子問題:k在新樹的訪問序列中的位置?
?
ò??思考:訪問序列P中k必然在k的后代之前,那么我們需要討論的是k與非k后代的節點的相對位置。現在有兩個選擇:一是先訪問k,然后訪問非k的后代的m個結點(i1,i2,...,im)?;二是先訪問非k的后代的m個結點(i1,i2,...,im),然后訪問k。
ò??我們需要知道怎樣的相對位置使最終代價最小。
ò??當前決策k完成時第二種選擇相對于第一種選擇費用之差為:?
ò??F2-F1=(Ci+Cj)×m-{sigma(Cik)|k=1..m}×2?
ò??也就是說,第二種方案先訪問m個節點,這m個節點相比第一種方案提前了2個時間點,那么減少的費用是2×{sigma(Cik)|k=1..m};后訪問i和j,i,j相比第一種方案延后了m個時間點,增加的費用是(Ci+Cj)×m。?
???
ò??1.標記根節點為第一個訪問的節點。
ò??2.求出當前未訪問節點中的最大點權節點i。
ò??3.將i和它的父親j合并為一個節點,節點權值為兩者權值的算術平均數。在序列P中將j的后繼置為i。同時更新樹的信息。
ò??4.若當前樹中節點數大于1,則轉第2步。
ò??5.樹的大小為1時算法結束。
ò??6.掃描求得的P序列得到答案。
ò??時間復雜度:O(N^2)。
AC:
#include <iostream> #include <cstdio> using namespace std;const int N = 1007; struct node {int t; //time[]int p; //記錄父節點int c; //c[]double w; //w[] }num[N]; int n, r;int find() //查找最大的權值,返回其位置 {int pos;double max = 0;for(int i = 1; i <= n; i++)if(num[i].w > max && i != r) //pos不能為根節點{max = num[i].w;pos = i;}return pos; }int main() {int i, j, a, b, pos, ans, f;while(scanf("%d%d", &n, &r), n||r){ans = 0;for(i = 1; i <= n; i++){scanf("%d", &num[i].c); num[i].w = num[i].c; //初始時w[i]置為c[i];num[i].t = 1; //time[i] 置為1;ans += num[i].c; //初始ans = sum(c[i]);}for(i = 1; i < n; i++){scanf("%d%d", &a, &b);num[b].p = a; //記錄父節點,建立樹關系}i = n;while(i > 1){pos = find(); //找到最大權值的位置num[pos].w = 0; //找到后將之置0,否則影響下次查找。f = num[pos].p; //f為父節點ans += num[pos].c * num[f].t; //將找到的c[pos]*time[f]加入ansfor(j = 1; j <= n; j++)if(num[j].p == pos)num[j].p = f; //如果有節點與pos相連,讓它指向pos的父節點num[f].c += num[pos].c; //C_i = c[該節點] + c[父節點]num[f].t += num[pos].t; //T_i = time[該節點]+time[父節點]num[f].w = (double)num[f].c/num[f].t; //合并后的f節點的權值i--;}printf("%d\n", ans);}return 0; }
總結
以上是生活随笔為你收集整理的hdu 1055(贪心)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nyoj 47
- 下一篇: Oracle递归查询示例分析