2020 秦皇島 CCPC K題 貪心(樹形dp)
阿新 • • 發佈:2020-10-21
@
目錄題意
一顆\(n(1e6)\)個節點的有根樹,本部在\(1\)號點,每秒鐘可以從本部派遣一支軍隊出發,也可以移動一隻在外的軍隊。不管是派遣還是移動都只能沿著一條邊移動。問最短時間經過所有的點。
解析
- 首先把所有出邊按照最深葉子深度從小到大排序。精髓排序。
- 然後在搜尋一邊,同時記錄上一隻停留的軍隊所在的葉子標號,如果從這個停留的軍隊出發比根節點出發更優就選擇這個軍隊繼續出發,否則從根節點再派遣一支軍隊並拋棄那隻軍隊。因為它以後也必不可能對其他路徑產生貢獻!排序保證了這點!
- 然後遇到葉子就累計答案。
程式碼有註釋。
AC_CODE
一次排序就要\(1500ms\)。
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> #include <iostream> #include <assert.h> using namespace std; #define debug(x) cout << #x << ": " << x << "\n"; #define debug2(x, y) cout << #x << ": " << x << ", " << #y << ": " << y << "\n"; const int maxn = 1e6 + 7; const double eps = 1e-8; typedef long long ll; int n; int dep[maxn], ye[maxn]; vector<int> adj[maxn]; void dfs(int u, int d) { dep[u] = d; ye[u] = 0; for(int v: adj[u]) { dfs(v, d + 1); ye[u] = max(ye[u], ye[v] + 1);//子樹最深葉子深度 } } ll ans; int id; bool cmp(const int &a, const int &b) { if(ye[a] != ye[b]) return ye[a] < ye[b]; return a < b; } void dfs2(int u, int ba, int h, int d) { //cout << u << " " << d << endl; if(adj[u].size() == 0) { ans += d; id = u;//上一次軍隊停留的葉子 return; } for(int v: adj[u]) { if(id == 0) {//這條路徑前沒有可產生貢獻的軍隊停留,徑直往下走 dfs2(v, u, h + 1, d + 1); }else { int w = dep[id] - dep[u]; if(w < h) {//如果從上次軍隊停留的葉子出發比從根節點出發更優 id = 0;//軍隊就繼續出發,所以就沒有軍隊停留了 dfs2(v, u, h + 1, w + 1); }else { id = 0;//上個葉子對這條路徑產生不了貢獻,以後也必不可能產生貢獻 dfs2(v, u, h + 1, h + 1); } } } } int main() { //freopen("in.txt", "r", stdin); int T;scanf("%d",&T); int kase = 0; while(T--) { id = 0; ans = 0; scanf("%d", &n); for(int i = 2; i <= n; ++i) { int f; scanf("%d", &f); adj[f].push_back(i); } dfs(1, 0); //按最深子葉深度從小到大排序,排序是精髓 for(int i = 1; i <= n; ++i) sort(adj[i].begin(), adj[i].end(), cmp); dfs2(1, 0, 0, 0); printf("Case #%d: %lld\n", ++kase, ans); for(int i = 1; i <= n; ++i) adj[i].clear(); } return 0; } /* 7 14 1 2 3 3 5 3 7 8 2 10 11 12 13 3 1 1 6 1 2 3 4 4 9 1 2 2 4 5 1 7 8 12 1 2 3 1 5 6 4 4 12 9 11 7 1 2 3 3 2 5 6 1 2 3 3 2 17 2 6 9 12 8 7 */