1. 程式人生 > >BZOJ 4472 salesman 題解

BZOJ 4472 salesman 題解

## 題目 某售貨員小T要到若干城鎮去推銷商品,由於該地區是交通不便的山區,任意兩個城鎮之間都只有唯一的可能經過其它城鎮的路線。小T可以準確地估計出在每個城鎮停留的淨收益。這些淨收益可能是負數,即推銷商品的利潤抵不上花費。由於交通不便,小T經過每個城鎮都需要停留,在每個城鎮的停留次數與在該地的淨收益無關,因為很多費用不是計次收取的,而每個城鎮對小T的商品需求也是相對固定的,停留一次後就飽和了。每個城鎮為了強化治安,對外地人的最多停留次數有嚴格的規定。請你幫小T設計一個收益最大的巡迴方案,即從家鄉出發,在經過的每個城鎮停留,最後回到家鄉的旅行方案。你的程式只需輸出最大收益,以及最優方案是否唯一。方案並不包括路線的細節,方案相同的標準是選擇經過並停留的城鎮是否相同。因為取消巡迴也是一種方案,因此最大收益不會是負數。小T在家鄉淨收益是零,因為在家鄉是本地人,家鄉對小T當然沒有停留次數的限制。 ## 輸入格式 輸入的第一行是一個正整數$n(5<=n<=100000)$,表示城鎮數目。城鎮以$1$到$n$的數命名。小T的家鄉命名為$1$。第二行和第三行都包含以空格隔開的$n-1$個整數,第二行的第$i$個數表示在城鎮$i+1$停留的淨收益。第三行的第$i$個數表示城鎮$i+1$規定的最大停留次數。所有的最大停留次數都不小於$2$。接下來的$n-1$行每行兩個1到$n$的正整數$x$,$y$,之間以一個空格隔開,表示$x$,$y$之間有一條不經過其它城鎮的雙向道路。輸入資料保證所有城鎮是連通的。 ## 輸出格式 輸出有兩行,第一行包含一個自然數,表示巡迴旅行的最大收益。如果該方案唯一,在第二行輸出"solution is unique",否則在第二行輸出"solution is not unique"。 ## 輸入樣例 ```cpp 9 -3 -4 2 4 -2 3 4 6 4 4 2 2 2 2 2 2 1 2 1 3 1 4 2 5 2 6 3 7 4 8 4 9 ``` ## 輸出樣例 ``` 9 solution is unique ``` ## 樣例解釋 最佳路線包括城鎮 1,2, 4, 5, 9 ## 題解 **任意兩個城鎮之間都只有唯一的可能經過其它城鎮的路線**,說明這一定是一棵樹 ### 第一問 這道題明顯是樹形動規,但加了一個限制條件,就是最大停留次數,聯想樹形動規時的形式,這個最大停留次數其實和能訪問的子樹個數有關係. ![](https://img2020.cnblogs.com/blog/1975074/202004/1975074-20200405123516838-1433076673.png) 上圖表示了一個DFS過程,其中2,3,4,5,6號都是子樹,若將這些子樹看成一個點,則DFS過程中經過的節點為 ``` [1] 2 [1] 2 [1] 4 [1] 5 [1] 6 [1] ``` 注意其中的根節點1,出現了6次,本題中就是停留了6次,而1號節點有5棵子樹,可以發現,若i節點最大停留次數為$limit[x]$,則DFS中最多能訪問$limit[x]-1$棵子樹 這些子樹中對根節點dp的貢獻不同,我們當然要選擇其中最大的,所以排一下序,選其中前$limit[x]-1$棵子樹來更新根節點的dp值. 注意,$limit[x]-1$也存在大於子樹個數的情況,所以實際操作的時候要取$limit[x]$和字數個數的最小值作為更新根節點的子樹數量,由於淨收益可能是負數,所以更新的時候發現是負數立刻停止即可. 所以遍歷子樹(已排序)時候,條件為 ``` soni < min(limit[root] - 1, sontot) && dp[sonn[soni + 1]] >= 0 ``` 其中,`soni`為當前迴圈遍歷的子樹時的迴圈變數(注意不時子樹根節點編號),`root`為根節點編號,`sontot`為子樹的數量,`sonn`陣列儲存子樹的根節點編號,`sonn[soni+1]`為這次迴圈的子樹編號(因為soni在迴圈內自增1) 還有一個條件 >家鄉對小T當然沒有停留次數的限制 也就是整棵樹的根節點無限制次數,那麼只需要在DFS之前將根節點的`limit`值賦值為無限大即可 ### 第二問 顯然,根節點方案是否唯一首先要看其子樹,如果有任意一棵更新了根節點dp值得子樹的方案不唯一,根節點的方案顯然也不唯一. 除此之外,還有存在子樹方案唯一但根節點選取子樹的方案不唯一的情況. 遍歷子樹的時候,將 子樹方案是否唯一的值 和 根節點選取子樹的方案是否唯一 的值進行或運算(只要有一個為真,結果就為真),得到的結果就是這棵樹的方案是否唯一,最後輸出即可. 那麼怎麼判斷根節點選取子樹的方案是否唯一呢,有兩種情況: 1. 相同值引起的不唯一 假設排好序後的子樹dp值為`10 9 8 7 6 6 5 4 3 2 1`,而你只能選5個(`limit`值為6),顯然你選擇的是`10 9 8 7 6`,但是仔細觀察,你會發現還有一個相同的6,那麼我能不能拋棄第一個6選擇第二個6呢?當然可以,那麼,這就有了兩種選擇辦法(`[10] [9] [8] [7] [6] 6 5 4 3 2 1`和`[10] [9] [8] [7] 6 [6] 5 4 3 2 1`),這種情況不需要注意`limit`值的限制,因為沒有多選擇一個,是拋棄一個再選一個,用來更新的子樹數量沒有改變. 2. dp值為0引起的不唯一 假設排好序後的子樹dp值為`10 9 8 7 6 0`,而你只能選5個(limit值為6),你可以選擇`10 9 8 7 6`,也可以選擇`10 9 8 7 6 0`,這兩種方案更新的值都是一樣的,這種情況需要注意`limit`值的限制,即使`limit`值為0,如果選擇了,也會多一次停留,雖然資料有點水,親測加不加limit值的限制都能A. 出現這兩種情況時,直接將這棵樹方案是否唯一的賦值為真即可 ## 程式碼 ```cpp #include #include #include using namespace std; const int N = 100000; struct edge { int i, next; } edges[2 * N + 5]; int head[N + 5], tot, n, w[N + 5], limit[N + 5], dp[N + 5], ansn[N + 5],sonn[N + 5]; void add(int u, int v) { edges[++tot].i = v; edges[tot].next = head[u]; head[u] = tot; } bool cmp(int a, int b) { return dp[a] > dp[b]; } void dfs(int root, int f) { dp[root] = w[root]; int sontot = 0, soni = 0; for (int i = head[root]; i; i = edges[i].next) if (edges[i].i != f) dfs(edges[i].i, root); for (int i = head[root]; i; i = edges[i].next) if (edges[i].i != f) sonn[++sontot] = edges[i].i; sort(sonn + 1, sonn + 1 + sontot, cmp); while (soni < min(limit[root] - 1, sontot) && dp[sonn[soni + 1]] >= 0) dp[root] += dp[sonn[++soni]], ansn[root] |= ansn[sonn[soni]];//按位或 if (soni < sontot && soni > 0 && dp[sonn[soni]] == dp[sonn[soni + 1]] || dp[sonn[soni]] == 0 && soni > 0 && soni <= limit[root] - 1)//兩種情況,注意邊界 ansn[root] = 1; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) scanf("%d", &w[i + 1]); for (int i = 1; i < n; i++) scanf("%d", &limit[i + 1]); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); } limit[1] = n + 1;//在家鄉沒有停留限制 dfs(1, 0); printf("%d\n%s", dp[1], ansn[1] ? "solution is not unique" : "solution is unique"); return 0