【Luogu P3241 & SP2939】[HNOI2015]开店 & QTREE5
总览
假设现在有一个点分治可以做的题,但是因为被强制在线(带修或者其他原因)。显然有一种做法是每询问一次就点分一次,时间复杂度是\(O(mn\log n)\),是一个并不太优秀的做法。
考虑到每次点分治都需要重新找一次重心以及统计信息等等,而事实上由于树的形态并不会改变,也就是说重心其实是并不会变化的。那么,找重心等等重复操作其实是可以省下来的。
于是点分树这种结构就诞生了。
考虑点分治的过程,每次找到重心做完统计之后相当于把原树拆成了多个部分,递归地进行操作。那么我们把每次的重心拿出来,剩下的各个部分分别找到重心之后,往当前的重心连一条边,就可以得到一颗重构后的树——点分树。
事实上由于重心的性质,这棵树的深度不会超过\(\log n\)层。因此我们在统计答案或者修改时只需要从当前的点暴力地往上跳就可以了。
于是只要在各个点上按照各个题目的需要维护一些奇奇怪怪的东西就可以解决问题了,通常会使用到线段树、树状数组、可删堆之类的数据结构。
例题1:SP2939 QTREE5
题意不再赘述。
对于这一类树的形态固定,且答案没有对父子关系的要求的题目,我们可以考虑点分治做法。
思考一个简化的问题,假定没有修改颜色,有多组询问。
一个很显然的做法是,在点分治时对于当前的重心找到距离它最近的白点,然后加上当前重心与询问的\(v\)点之间的距离。
注意一个细节,这里并不需要考虑最近的白点和\(v\)点在同一个子树的情况,因为这种情况得到的答案一定不是最优解。所以如果此题改为最大值,那么需要讨论的情况会变得很麻烦。
那么加上修改的操作之后呢?
建一棵点分树,然后对每一个点维护一个数据结构,支持查询最小值,插入,删除三个操作。
那么我们可以使用可删堆(好像线段树也可以)。
于是这题就做完了。
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=100005;
struct HEAP
{
priority_queue<int,vector<int>,greater<int> > q1,q2;
void maintain()
{
while (!q1.empty()&&!q2.empty()&&q1.top()==q2.top()) q1.pop(),q2.pop();
}
int top()
{
maintain();
return q1.top();
}
bool empty()
{
maintain();
return q1.empty();
}
void push(int x)
{
q1.push(x);
maintain();
}
void erase(int x)
{
q2.push(x);
maintain();
}
}que[maxn];
struct DATA
{
int to,next,val;
}edge[maxn<<1];
int head[maxn],cnt,n,q,u,v,size[maxn],dis[maxn],recsize,root,tot_size,fa[maxn],pts,opt,son[maxn],d[maxn],F[maxn],top[maxn];
bool tag[maxn],col[maxn];
inline int read()
{
int ret=0;char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
return ret;
}
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void dfs1(int u,int f)
{
size[u]=1;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==f) continue;
dis[v]=dis[u]+1;
dfs1(v,u);
size[u]+=size[v];
if (size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int f,int t)
{
F[u]=f;
d[u]=d[f]+1;
top[u]=t;
if (son[u]) dfs2(son[u],u,t);
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==f||v==son[u]) continue;
dfs2(v,u,v);
}
}
inline int query_LCA(int x,int y)
{
while (top[x]!=top[y])
{
if (d[top[x]]<d[top[y]]) swap(x,y);
x=F[top[x]];
}
return d[x]>d[y]?y:x;
}
void get_root(int u,int f)
{
size[u]=1;int maxsize=0;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (tag[v]||v==f) continue;
get_root(v,u);
size[u]+=size[v];
maxsize=max(maxsize,size[v]);
}
maxsize=max(maxsize,tot_size-size[u]);
if (maxsize<recsize)
{
recsize=maxsize;
root=u;
}
}
void get_size(int u,int f)
{
size[u]=1;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (tag[v]||v==f) continue;
get_size(v,u);
size[u]+=size[v];
}
}
void build(int u)
{
tag[u]=true;
get_size(u,0);
if (size[u]==1) return ;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (tag[v]) continue;
recsize=0x3f3f3f3f;
tot_size=size[v];
get_root(v,u);
fa[root]=u;
build(root);
}
}
inline int get_dis(int x,int y)
{
return dis[x]+dis[y]-2*dis[query_LCA(x,y)];
}
void update(int u,int c)
{
if (u==0) return ;
int t=get_dis(u,pts);
if (c==1)
que[u].push(t);
else
que[u].erase(t);
update(fa[u],c);
}
int query(int u)
{
if (u==0) return 0x3f3f3f3f;
int ret=0x3f3f3f3f;
int t=get_dis(u,pts);
if (!que[u].empty()) ret=min(que[u].top()+t,ret);
return min(ret,query(fa[u]));
}
int main()
{
n=read();
for (int i=1;i<n;i++)
{
u=read(),v=read();
add(u,v),add(v,u);
}
recsize=0x3f3f3f3f;
tot_size=n;
get_root(1,0);
dfs1(root,0);
dfs2(root,0,root);
build(root);
q=read();
int now=0;
for (int i=1;i<=q;i++)
{
opt=read(),pts=read();
if (opt==0)
{
col[pts]=!col[pts];
if (col[pts]==1) now++;
else now--;
update(pts,col[pts]);
}
else
{
if (col[pts]==1) printf("0\n");
else if (now==0) printf("-1\n");
else {printf("%d\n",query(pts));}
}
}
return 0;
}
例题2:Luogu P3241 开店
同样先考虑暴力的做法。
对于每个点记录其子树内节点到自己的距离,统计答案时加上到询问点的距离即可。
可以考虑在对年龄离散化后使用线段树,这里使用的是更暴力的vector。
对于每一个点开一个vector,记录其下属每一个节点到它的距离以及对应节点的年龄。
排序后就可以利用前缀和直接求出对应区间的和。
这一题有一个需要注意的地方,如果询问点和另一个端点来自重心的同一棵子树就会出现统计重复的问题。所以在统计时要去掉询问点所在子树对当前重心的贡献。
因此每个节点需要维护两个vector,一个是下属节点到自身的信息,一个是下属节点到自身父亲的信息。
#include<cstdio>
#include<vector>
#include<algorithm>
#define mid ((l+r)>>1)
#define ll long long
using namespace std;
const int maxn=1.5e5+5;
struct DATA
{
int to,next;ll val;
}edge[maxn<<1];
struct REC
{
ll val;int id;
bool operator< (const REC &x) const
{
return id<x.id;
}
};
int cnt,head[maxn],F[maxn],son[maxn],size[maxn],d[maxn],top[maxn];
bool tag[maxn];
ll dis[maxn],w;
vector<REC> vec[2][maxn];
int tot_size,recsize=0x3f3f3f3f,root,fa[maxn],pts,last,A,AA,BB,a[maxn],n,Q,u,v;
ll ans;
void add(int u,int v,ll w)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
edge[cnt].val=w;
head[u]=cnt;
}
void dfs1(int u,int f)
{
F[u]=f;
d[u]=d[f]+1;
size[u]=1;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==f) continue;
dis[v]=dis[u]+edge[i].val;
dfs1(v,u);
size[u]+=size[v];
if (size[son[u]]<size[v]) son[u]=v;
}
}
void dfs2(int u,int t)
{
top[u]=t;
if (son[u]) dfs2(son[u],t);
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==F[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int query_LCA(int x,int y)
{
while (top[x]!=top[y])
{
if (d[top[x]]<d[top[y]]) swap(x,y);
x=F[top[x]];
}
return d[x]<d[y]?x:y;
}
inline ll getdis(int x,int y)
{
return dis[x]+dis[y]-2*dis[query_LCA(x,y)];
}
void get_root(int u,int f)
{
size[u]=1;int maxsize=0;
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==f||tag[v]) continue;
get_root(v,u);
size[u]+=size[v];
maxsize=max(size[v],maxsize);
}
maxsize=max(maxsize,tot_size-size[u]);
if (maxsize<recsize)
{
recsize=maxsize;
root=u;
}
}
void getsize(int u,int f,int sum)
{
size[u]=1;
// REC tmp=REC{getdis(u,root),a[u]};
vec[0][root].push_back(REC{sum,a[u]});
if (fa[root]) vec[1][root].push_back(REC{getdis(u,fa[root]),a[u]});
for (int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if (tag[v]||v==f) continue;
getsize(v,u,sum+edge[i].val);
size[u]+=size[v];
}
}
void build(int x)
{
tag[x]=true;
getsize(x,0,0);
for (int i=head[x];i;i=edge[i].next)
{
int v=edge[i].to;
if (tag[v]) continue;
recsize=0x3f3f3f3f;
tot_size=size[v];
get_root(v,x);
fa[root]=x;
build(root);
}
}
inline int read()
{
int ret=0;char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
return ret;
}
inline ll READ()
{
ll ret=0;char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
return ret;
}
signed main()
{
n=read(),Q=read(),A=read();
for (int i=1;i<=n;i++)
{
a[i]=read();
a[i]++;
}
for (int i=1;i<n;i++)
{
u=read(),v=read(),w=READ();
add(u,v,w),add(v,u,w);
}
dfs1(1,0);
dfs2(1,0);
tot_size=n;
get_root(1,0);
tag[root]=true;
build(root);
// for (int i=0;i<vec[0][2].size()-1;i++) printf("Case%d: %d %d\n",i+1,vec[0][2][i].val,vec[0][2][i].id);
for (int i=1;i<=n;i++)
{
sort(vec[0][i].begin(),vec[0][i].end());
sort(vec[1][i].begin(),vec[1][i].end());
vec[0][i].push_back(REC{0,A+1});
vec[1][i].push_back(REC{0,A+1});
for (int j=vec[0][i].size()-2;j>-1;j--) vec[0][i][j].val+=vec[0][i][j+1].val;
for (int j=vec[1][i].size()-2;j>-1;j--) vec[1][i][j].val+=vec[1][i][j+1].val;
}
// puts("fuckyou");
// for (int i=0;i<vec[0][2].size();i++) printf("Case%d: %d %d\n",i,vec[0][2][i].val,vec[0][2][i].id);
for (int i=1;i<=Q;i++)
{
u=read(),AA=read(),BB=read();
int L,R;
L=min((AA+ans)%A,(BB+ans)%A),R=max((AA+ans)%A,(BB+ans)%A);
L++,R++;
int x=u;
ll ret=0;
while (x!=0)
{
int LL=lower_bound(vec[0][x].begin(),vec[0][x].end(),REC{0,L})-vec[0][x].begin();
int RR=upper_bound(vec[0][x].begin(),vec[0][x].end(),REC{0,R})-vec[0][x].begin();
ret+=vec[0][x][LL].val-vec[0][x][RR].val+(RR-LL)*getdis(x,u);
if (fa[x]!=0)
{
int LL=lower_bound(vec[1][x].begin(),vec[1][x].end(),REC{0,L})-vec[1][x].begin();
int RR=upper_bound(vec[1][x].begin(),vec[1][x].end(),REC{0,R})-vec[1][x].begin();
ret-=vec[1][x][LL].val-vec[1][x][RR].val+(RR-LL)*getdis(fa[x],u);
}
x=fa[x];
}
printf("%lld\n",ans=ret);
}
return 0;
}