【樹形DP + 貪心】2020CCPC秦皇島-K. Kingdom's Power
【樹形DP + 貪心】2020CCPC秦皇島-K. Kingdom's Power
題目連結(https://codeforces.com/gym/102769/problem/K)
K. Kingdom's Power
time limit per test 2 seconds
memory limit per test 512 megabytes
input standard input
output standard output
Alex is a professional computer game player.
These days, Alex is playing a war strategy game. The kingdoms in the world form a rooted tree. Alex's kingdom \(1\)
Alex has almost infinite armies, and all of them are located at \(1\) initially. Every week, he can command one of his armies to move one step to an adjacent kingdom. If an army reaches a kingdom, that kingdom will be captured by Alex instantly.
Alex would like to capture all the kingdoms as soon as possible. Can you help him?
Input
The first line of the input gives the number of test cases, \(T (1≤T≤10^5)\). \(T\) test cases follow.
For each test case, the first line contains an integer \(n (1≤n≤10^6)\), where nn is the number of kingdoms in the world.
The next line contains \((n−1)\)integers \(f2,f3,⋯,fn (1≤fi<i)\), representing there is a road between \(fi\) and \(i\).
The sum of nn in all test cases doesn't exceed \(5×10^6\).
Output
For each test case, output one line containing "Case #x: y", where \(x\) is the test case number (starting from \(1\)), and \(y\) is the minimum time to conquer the world.
Example
input
2
3
1 1
6
1 2 3 4 4
output
Case #1: 2
Case #2: 6
題意
給出一個有$n $個節點的有根樹,\(1\) 為根節點,根節點有無窮多個兵,每一秒可以讓任意一個兵向任意一個地方移動一步,兵所到的點被佔領,問最少需要經過多少秒,才能將所有的點都佔領
思路
樣例解釋
第一反應是dfs,但加上時間的限制,並且需要有貪心的操作,就需要做另外的處理。
如果所有的子節點都被佔領,則經過的點都會被佔領,那麼問題轉換為了到每個子節點的最短時間。
做法:用樹形dp維護從根節點走更優還是從最近子結點更優,更新答案。維護一個變量表示到達當前節點的深度,遞迴返回最近的子節點過來的距離,維護距離與深度的最小值。
(特殊)假設只有一個士兵,對於每個子樹而言,最長的鏈只走一次,其餘的鏈都會走兩次
因此,對於每個子樹,按照 最長鏈 進行排序,因為最後只走一次其最長鏈
AC程式碼
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
using namespace std;
typedef long long ll;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
const double pi = acos(-1.0);
int t, n;
vector<PII>tree[N];//節點
int dep[N];//存深度
int cnt[N];//存距離
int res;
int dfs1(int u, int index)//index是當前點的深度
{
if (tree[u].empty()) return 1;//當前點沒有子節點,貢獻為1
dep[u] = index;//更新深度
for (auto &it : tree[u])
{
it.first = max(it.first, dfs1(it.second, index + 1));//更新最長鏈
}
sort(tree[u].begin(), tree[u].end());//按照最長鏈排序
return tree[u].back().first + 1;//有子節點,貢獻 最深深度 + 1
}
int dfs2(int u, int index)//index是到達當前點的最短距離
{
cnt[u] = index;//更新距離
if (tree[u].empty()) return 1;//當前點沒有子節點,貢獻為1
int mi = index;//到當前節點的最近距離
for (auto it : tree[u])
{
mi = min(dep[u], dfs2(it.second, mi + 1));//維護距離與深度的最小值
}
return mi + 1;//有子節點,貢獻 最短距離 + 1
}
void init() {//初始化
for (int i = 0; i <= n; i++) {
tree[i].clear();
}
res = 0;
return;
}
void solve() {
init();
cin >> n;
int x;
for (int i = 2; i <= n; i++) {//n - 1個節點
cin >> x;
tree[x].push_back({ 0,i });//初始存入子節點編號,深度未更新,初始0
}
//進行兩次dfs維護最優解
dfs1(1, 0);
dfs2(1, 0);
for (int i = 1; i <= n; i++) {
if (tree[i].empty()) {//當前點沒有子節點,即為最深點,更新答案
res += cnt[i];
}
}
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> t;
for (int i = 1; i <= t; i++) {
solve();
cout << "Case #" << i << ": " << res << endl;//輸出
}
return 0;
}
/*
i raised a cute kitty in my code,
my friend who pass by can touch softly on her head:)
/l、
Meow~(゚、 。7
|、 ~ヽ
じしf_,)ノ
*/