CF526G Spiders Evil Plan
作者:互联网
非常不错的一道题。
题解
首先我们考虑没有 \(x\) 的限制,如果我们选择 \(y\) 条路径,最优的选法是什么?
首先可以证明,最后的 \(y\) 条路径必然是一个连通块,因为如果不是一个连通块,必然可以通过交换两条路径的交点来合并连通块,于是最后就合并为了一个连通块。这样的话,问题就被我们转换为了选择 $ 2y$ 个叶子,使得这些叶子所包含的连通块边权最大。
我们易证得直径的一端点必然位于答案中,这样如果我们以直径的一端点为根,问题就转化为了选择 \(2y-1\) 个叶子,打通其到根的路径,问连通块边权最大多少,这是一个长剖的板题。
但是考虑到有多次询问,需要预处理。
以上就是没有 \(x\) 的限制的时候该怎么做,如果考虑到 \(x\) 的限制,意味着我们需要删掉一个叶子再加入 \(x\) 所在长链的叶子节点,可以证明(反证法什么的),删掉的叶子只能是两种情况:
- 其是排名为 \(2y-1\) 的长链。
- 是加入的 \(x\) 所在长链碰到的第一个叶子。
然后用倍增暴力维护一下就行了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,q,rt1,rt2,lstans=0;
struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
namespace find_diameter{
int dep[N];
void find(int u,int fa,int &res){
if(dep[u]>dep[res]) res=u;
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa) continue;
dep[v]=dep[u]+e[i].val,find(v,u,res);
}
}
int main(){
dep[1]=0,find(1,0,rt1);
dep[rt1]=0,find(rt1,0,rt2);
return 0;
}
}
struct Node{int fa[20],sum[20],son,dep,dis,top;}tr[2][N];
int len[2],ord[2][N],sum[2][N];priority_queue<pair<int,int> > s;
void dfs1(int u,int tag){
tr[tag][u].top=u;
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v==tr[tag][u].fa[0]) continue;
tr[tag][v].fa[0]=u,tr[tag][v].dep=tr[tag][u].dep+e[i].val;
dfs1(v,tag),tr[tag][v].dis+=e[i].val,tr[tag][v].sum[0]=e[i].val;
tr[tag][u].dis=max(tr[tag][u].dis,tr[tag][v].dis);
if(tr[tag][v].dis>=tr[tag][tr[tag][u].son].dis) tr[tag][u].son=v;
}
}
void dfs2(int u,int tag){
if(tr[tag][u].son){
tr[tag][tr[tag][u].son].top=tr[tag][u].top;
dfs2(tr[tag][u].son,tag);
}
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v!=tr[tag][u].fa[0]&&v!=tr[tag][u].son) dfs2(v,tag);
}
}
void init(){
for(int j=1;j<20;++j){
for(int i=1;i<=n;++i){
tr[0][i].fa[j]=tr[0][tr[0][i].fa[j-1]].fa[j-1];
tr[0][i].sum[j]=tr[0][i].sum[j-1]+tr[0][tr[0][i].fa[j-1]].sum[j-1];
tr[1][i].fa[j]=tr[1][tr[1][i].fa[j-1]].fa[j-1];
tr[1][i].sum[j]=tr[1][i].sum[j-1]+tr[1][tr[1][i].fa[j-1]].sum[j-1];
}
}
while(!s.empty()) s.pop();
for(int i=1;i<=n;++i){
if(tr[0][i].top==i) s.push(make_pair(tr[0][i].dis,i));
}
while(!s.empty()){
ord[0][s.top().second]=++len[0];
sum[0][len[0]]=sum[0][len[0]-1]+s.top().first,s.pop();
}
while(!s.empty()) s.pop();
for(int i=1;i<=n;++i){
if(tr[1][i].top==i) s.push(make_pair(tr[1][i].dis,i));
}
while(!s.empty()){
ord[1][s.top().second]=++len[1];
sum[1][len[1]]=sum[1][len[1]-1]+s.top().first,s.pop();
}
}
int solve1(int x,int k,int tag){
int tmp=x,res=tr[tag][x].dis-tr[tag][tmp].sum[0];
for(int j=19;j>=0;--j){
if(ord[tag][tr[tag][tr[tag][tmp].fa[j]].top]>k)
res+=tr[tag][tmp].sum[j],tmp=tr[tag][tmp].fa[j];
}
res+=tr[tag][tmp].sum[0],tmp=tr[tag][tmp].fa[0];
res-=tr[tag][tmp].dis,res+=tr[tag][tmp].sum[0];
return sum[tag][k]+res;
}
int solve2(int x,int k,int tag){
int tmp=x,res=tr[tag][x].dis-tr[tag][tmp].sum[0];
for(int j=19;j>=0;--j){
if(ord[tag][tr[tag][tr[tag][tmp].fa[j]].top]>k-1)
res+=tr[tag][tmp].sum[j],tmp=tr[tag][tmp].fa[j];
}
res+=tr[tag][tmp].sum[0],tmp=tr[tag][tmp].fa[0];
return sum[tag][k-1]+res;
}
int solve(int x,int k,int tag){
k=min(k,len[tag]);
if(ord[tag][tr[tag][x].top]<=k) return sum[tag][k];
return max(solve1(x,k,tag),solve2(x,k,tag));
}
int main(){
cin>>n>>q;
for(int i=1;i<n;++i){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
add(u,v,w,i<<1),add(v,u,w,i<<1|1);
}
find_diameter::main();
dfs1(rt1,0),dfs2(rt1,0),dfs1(rt2,1),dfs2(rt2,1),init();
for(int i=1;i<=q;++i){
int x,y;scanf("%d%d",&x,&y),x=(x+lstans-1)%n+1,y=(y+lstans-1)%n+1;
// printf("%d %d\n",x,y);
printf("%d\n",lstans=max(solve(x,2*y-1,0),solve(x,2*y-1,1)));
}
return 0;
}
/*
应该是考虑以直径的两端点为根做长剖?
这样整棵树就被我们分成若干条长链,选取其中的最长的 2y-1 条即可?
但是考虑到这样选出来的连通块可能不包含 x ,这个是需要特判的。
考虑到我们是需要将 x 到根的部分选上的,直接考虑暴力跳长剖就行了吧(倍增)。
*/
标签:tmp,int,CF526G,tr,Evil,fa,tag,Spiders,res 来源: https://www.cnblogs.com/Point-King/p/15217195.html