FTOUR2 - Free tour II
After the success of 2nd anniversary (take a look at problem FTOUR for more details), this 3rd year, Travel Agent SPOJ goes on with another discount tour.
The tour will be held on ICPC island, a miraculous one on the Pacific Ocean. We list N places (indexed from 1 to N) where the visitors can have a trip. Each road connecting them has an interest value, and this value can be negative (if there is nothing interesting to view there). Simply, these N places along with the roads connecting them form a tree structure. We will choose two places as the departure and destination of the tour.
Since September is the festival season of local inhabitants, some places are extremely crowded (we call them crowded places). Therefore, the organizer of the excursion hopes the tour will visit at most K crowded places (too tiring to visit many of them) and of course, the total number of interesting value should be maximum.
Briefly, you are given a map of N places, an integer K, and M id numbers of crowded place. Please help us to find the optimal tour. Note that we can visit each place only once (or our customers easily feel bored), also the departure and destination places don't need to be different.
Input
There is exactly one case. First one line, containing 3 integers N K M, with 1 <= N <= 200000, 0 <= K <= M, 0 <= M <= N.
Next M lines, each line includes an id number of a crowded place.
The last (N - 1) lines describe (N - 1) two-way roads connected N places, form a b i, with a, b is the id of 2 places, and i is its interest value (-10000 <= i <= 10000).
Output
Only one number, the maximum total interest value we can obtain.
Example
Input: 8 2 3 3 5 7 1 3 1 2 3 10 3 4 -2 4 5 -1 5 7 6 5 6 5 4 8 3 Output: 12
Explanation
We choose 2 and 6 as the departure and destination place, so the tour will be 2 -> 3 -> 4 -> 5 -> 6, total interest value = 10 + (-2) + (-1) + 5 = 12
* Added some unofficial cases
题目大意:一棵树,每条边都有一个边权,每个点要么是黑点要么是白点,求边权和最大的,经过的黑点数不超过k的路径.
分析:路径问题想着不能树剖,就点分治啦.一条路径要么经过重心,要么完全在重心的子树内,这一部分可以递归得到,对于穿过重心的路径,路径的两个端点肯定在两个不同的子树内.用一个数组d记录经过≤k个黑点的最长距离.当求到第i个子树的时候,在前i-1个子树中找d[k - j]的最大值,j是枚举的在当前子树中走过的黑点数.因为再枚举一遍前i-1个子树非常花时间,所以将这些子树的答案全部合并.
直接合并无异于暴力,可以知道合并的复杂度是两个子树的黑点的最大值.根据贪心,可以将这些子树按照黑点从小到大排序,依次合并.
因为d记录的是≤k的答案,所以在满足限制的情况下可以由d[i-1]更新d[i],就不用再枚举一遍了.
n=2的情况要特殊处理一下.
#include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 400010,inf = 0x7fffffff; int n,k,m,flag[maxn],head[maxn],to[maxn],nextt[maxn],w[maxn],tot = 1,dist[maxn],temp; int f[maxn],root,sizee[maxn],sum,vis[maxn],ans,d[maxn],num[maxn],nummax,mx[maxn]; vector <pair<int,int> >e; void add(int x,int y,int z) { w[tot] = z; to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void getroot(int u,int fa) { sizee[u] = 1; f[u] = 0; for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (v == fa || vis[v]) continue; getroot(v,u); sizee[u] += sizee[v]; f[u] = max(f[u],sizee[v]); } f[u] = max(f[u],sum - sizee[u]); if (f[u] < f[root]) root = u; } void getdis(int u,int fa) { nummax = max(nummax,num[u]); for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (v == fa || vis[v]) continue; d[v] = d[u] + w[i]; num[v] = num[u] + flag[v]; getdis(v,u); } } void getmax(int u,int fa) { dist[num[u]] = max(dist[num[u]],d[u]); for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (v == fa || vis[v]) continue; getmax(v,u); } } void solve(int u) { vis[u] = 1; e.clear(); if (flag[u]) k--; for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (vis[v]) continue; num[v] = flag[v]; d[v] = w[i]; nummax = 0; getdis(v,u); e.push_back(make_pair(nummax,v)); } sort(e.begin(),e.end()); for (int i = 0; i < e.size(); i++) { getmax(e[i].second,u); int res = 0; if (i != 0) { for (int j = e[i].first; j >= 0; j--) { while (res + 1 + j <= k && res + 1 <= e[i - 1].first) //res + 1 从 res转移而来,所以res要满足i-1的条件 { res++; mx[res] = max(mx[res],mx[res - 1]); } if (res + j <= k) ans = max(ans,mx[res] + dist[j]); } } if (i != e.size() - 1) { for (int j = 0; j <= e[i].first; j++) { mx[j] = max(mx[j],dist[j]); dist[j] = 0; } } else { for (int j = 0; j <= e[i].first; j++) mx[j] = dist[j] = 0; } } if (flag[u]) k++; for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (vis[v]) continue; root = 0; f[0] = sum = sizee[v]; getroot(v,0); solve(root); } } int main() { scanf("%d%d%d",&n,&k,&m); for (int i = 1; i <= m; i++) { int x; scanf("%d",&x); flag[x] = 1; } for (int i = 1; i < n; i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if (i == 1) temp = c; add(a,b,c); add(b,a,c); } f[0] = sum = n; getroot(1,0); solve(root); if (n == 2 && k >= m) ans = temp; printf("%d ",ans); return 0; }