SPOJ: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
题意:给定一棵边权树,树上的点有M个为黑色,其余为白色。现在求简单路径的最大权,满足路径上的黑点不超过K个。
思路:不难想到是分治题。把问题转化为子问题:求经过根节点的不超过K个黑点的最大权路径。然后对于每一个子问题,像树形DP那样乱搞就行。但是每个子问题的解决需要线性,才能得到NlogN的复杂度。此题的难点就是如何线性得到根的下面的若干个子树的信息。
具体的,我们把问题转化为若干棵树,每棵树把重心转化为根节点root;用dis[i]数组,表示从根节点向下经过i个黑色节点的最大权值。假设root下面有X个子树,对于当前前面的S个子树,我们已经维护好了这S个子树的dis数组。(A):对于第S+1个子树,我们用tdis[j]数组表示第S+1棵子树经过j个黑色节点的最大权值;对所有i+j<=K,dis[i]+tdis[j]来更新ans,这里可以利用更新前缀: dis[i]=min(dis[i],dis[i-1]) 来降低复杂度到线性。(B):而访问完第S+1棵树后,又用tdis数组去更新dis数组:dis[i]=min(dis[i], tdis[i]); 对于A部分,是线性的;而B部分,取决于子树X的多少,最坏可以到达N^2。 所以必须降低更新S部分,即每棵子树信息的复杂度。解决方案是启发式合并。 即每一层排序后处理,而每个点只有一次参加排序,所以排序复杂度也是O(NlgN)的。
没有优化的代码,A,B:超时。
#include<bits/stdc++.h> using namespace std; const int maxn=200010; const int inf=1e9+10; int Laxt[maxn],Next[maxn],To[maxn],cost[maxn],B[maxn],cnt,N,M,K; int dis[maxn],tdis[maxn],sz[maxn],son[maxn],vis[maxn],sn,root,ans; void add(int u,int v,int d){ Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; cost[cnt]=d; } void getroot(int u,int fa) { sz[u]=1; son[u]=0; for(int i=Laxt[u];i;i=Next[i]){ if(To[i]!=fa&&!vis[To[i]]){ getroot(To[i],u); sz[u]+=sz[To[i]]; son[u]=max(son[u],sz[To[i]]); } } son[u]=max(son[u],sn-son[u]); if(root==0||son[root]>son[u]) root=u; } void solve(int u,int fa,int used,int sum) { if(used>K) return ; tdis[used]=max(tdis[used],sum); ans=max(ans,sum+dis[K-used]); for(int i=Laxt[u];i;i=Next[i]){ if(!vis[To[i]]&&To[i]!=fa) solve(To[i],u,used+B[To[i]],sum+cost[i]); } } void dfs(int u) { vis[u]=1; dis[0]=0; for(int i=1;i<=K;i++) dis[i]=-inf; //A if(B[u]) K--; for(int i=Laxt[u];i;i=Next[i]) if(!vis[To[i]]){ for(int j=0;j<=K;j++) tdis[j]=-inf; //B solve(To[i],0,B[To[i]],cost[i]); //D线性部分 dis[0]=max(dis[0],tdis[0]); for(int j=1;j<=K;j++){ //C dis[j]=max(dis[j],dis[j-1]); dis[j]=max(dis[j],tdis[j]); ans=max(ans,dis[j]); } } if(B[u]) K++; for(int i=Laxt[u];i;i=Next[i]){ if(vis[To[i]]) continue; root=0; sn=sz[To[i]]; getroot(To[i],0); dfs(root); } } int main() { int u,v,x; scanf("%d%d%d",&N,&K,&M); for(int i=1;i<=M;i++) scanf("%d",&x),B[x]=1; for(int i=1;i<N;i++){ scanf("%d%d%d",&u,&v,&x); add(u,v,x); add(v,u,x); } root=0; sn=N; getroot(1,0); dfs(root); printf("%d\n",ans); return 0; }
优化的代码,B部分通过启发式合并。每次我们得到最深的深度,然后更新部分tdis数组只更新到最深的地方。
#include<bits/stdc++.h>。 using namespace std; const int maxn=200010; const int inf=0x7FFFFFFF; int Laxt[maxn],Next[maxn<<1],To[maxn<<1],cost[maxn<<1],B[maxn],cnt,N,M,K; int dis[maxn],tdis[maxn],sz[maxn],son[maxn],vis[maxn],sn,root,ans; struct in{ int dep,id; }s[maxn]; bool cmp(in w,in v){ return w.dep<v.dep; } void read(int &x){ x=0; int f=1; char c=getchar(); while(c>'9'||c<'0') { if(c=='-') f=-1;c=getchar();} while(c<='9'&&c>='0') x=(x<<3)+(x<<1)+c-'0',c=getchar(); x*=f; } void add(int u,int v,int d){ Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; cost[cnt]=d; } void getroot(int u,int fa) { sz[u]=1; son[u]=0; for(int i=Laxt[u];i;i=Next[i]){ if(To[i]!=fa&&!vis[To[i]]){ getroot(To[i],u); sz[u]+=sz[To[i]]; son[u]=max(son[u],sz[To[i]]); } } son[u]=max(son[u],sn-son[u]); if(root==0||son[root]>son[u]) root=u; } int getmaxdep(int u,int fa,int used) { if(used==K) return K; int res=used; for(int i=Laxt[u];i;i=Next[i]){ if(!vis[To[i]]&&To[i]!=fa){ res=max(res,getmaxdep(To[i],u,used+B[To[i]])); } }return res; } void solve(int u,int fa,int used,int sum) { if(used>K) return ; tdis[used]=max(tdis[used],sum); for(int i=Laxt[u];i;i=Next[i]){ if(!vis[To[i]]&&To[i]!=fa) solve(To[i],u,used+B[To[i]],sum+cost[i]); } } void dfs(int u) { vis[u]=1; dis[0]=0; if(B[u]) K--; int tot=0; for(int i=Laxt[u];i;i=Next[i]) if(!vis[To[i]]){ tot++; s[tot].id=i; s[tot].dep=getmaxdep(To[i],u,B[To[i]]); } sort(s+1,s+tot+1,cmp); for(int i=1;i<=tot;i++){ //这里优化的核心 solve(To[s[i].id],u,B[To[s[i].id]],cost[s[i].id]); if(i!=1) { for(int j=1;j<=s[i-1].dep;j++) dis[j]=max(dis[j],dis[j-1]); for(int j=s[i].dep;j>=0;j--) ans=max(ans,dis[min(s[i-1].dep,K-j)]+tdis[j]); //这里注意min一下,因为二者之和不一定能打到K,因为这里WA了很多次。 } for(int j=0;j<=s[i].dep;j++) dis[j]=max(dis[j],tdis[j]),tdis[j]=0; } for(int j=0;j<=s[tot].dep;j++){ ans=max(ans,max(dis[j],tdis[j])); dis[j]=tdis[j]=0; } if(B[u]) K++; for(int i=Laxt[u];i;i=Next[i]){ if(vis[To[i]]) continue; root=0; sn=sz[To[i]]; getroot(To[i],0); dfs(root); } } int main() { int u,v,x; scanf("%d%d%d",&N,&K,&M); for(int i=1;i<=M;i++) read(x),B[x]=1; for(int i=1;i<N;i++){ read(u); read(v); read(x); add(u,v,x); add(v,u,x); } root=0; sn=N; getroot(1,0); dfs(root); printf("%d\n",ans); return 0; }