[spojQTREE5]Query on a tree V

合理的正解大概是动态点分治,这里给出其实现

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define N 100005
  4 struct Edge{
  5     int nex,to;
  6 }edge[N<<1];
  7 multiset<int>S[N],mnS[N];
  8 int n,m,E,x,y,head[N],fa[N],vis[N];
  9 void add(int x,int y){
 10     edge[E].nex=head[x];
 11     edge[E].to=y;
 12     head[x]=E++;
 13 }
 14 namespace DIST{
 15     int dep[N],f[N][20];
 16     void dfs(int k,int fa,int s){
 17         f[k][0]=fa,dep[k]=s;
 18         for(int i=1;i<20;i++)f[k][i]=f[f[k][i-1]][i-1];
 19         for(int i=head[k];i!=-1;i=edge[i].nex)
 20             if (edge[i].to!=fa)dfs(edge[i].to,k,s+1);
 21     }
 22     int lca(int x,int y){
 23         if (dep[x]<dep[y])swap(x,y);
 24         for(int i=19;i>=0;i--)
 25             if (dep[f[x][i]]>=dep[y])x=f[x][i];
 26         if (x==y)return x;
 27         for(int i=19;i>=0;i--)
 28             if (f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
 29         return f[x][0];
 30     }
 31     int dis(int x,int y){
 32         return dep[x]+dep[y]-(dep[lca(x,y)]<<1);
 33     }
 34 };
 35 namespace DIVIDE{
 36     int rt,sz[N],vis[N];
 37     void get_sz(int k,int fa){
 38         sz[k]=1;
 39         for(int i=head[k];i!=-1;i=edge[i].nex)
 40             if ((!vis[edge[i].to])&&(edge[i].to!=fa)){
 41                 get_sz(edge[i].to,k);
 42                 sz[k]+=sz[edge[i].to];
 43             }
 44     }
 45     void get_rt(int k,int fa,int s){
 46         int mx=s-sz[k];
 47         for(int i=head[k];i!=-1;i=edge[i].nex)
 48             if ((!vis[edge[i].to])&&(edge[i].to!=fa)){
 49                 get_rt(edge[i].to,k,s);
 50                 mx=max(mx,sz[edge[i].to]);
 51             }
 52         if (mx<=(s>>1))rt=k;
 53     }
 54     int dfs(int k){
 55         get_sz(k,0);
 56         get_rt(k,0,sz[k]);
 57         k=rt,vis[k]=1;
 58         for(int i=head[k];i!=-1;i=edge[i].nex)
 59             if (!vis[edge[i].to])fa[dfs(edge[i].to)]=k;
 60         return k;
 61     }
 62 };
 63 void add(int k){
 64     if (!S[k].empty())mnS[fa[k]].insert(*S[k].begin());
 65 }
 66 void del(int k){
 67     if (!S[k].empty())mnS[fa[k]].erase(mnS[fa[k]].find(*S[k].begin()));
 68 }
 69 void Add(int k){
 70     mnS[k].insert(0);
 71     for(int i=k;fa[i];i=fa[i]){
 72         del(i);
 73         S[i].insert(DIST::dis(k,fa[i]));
 74         add(i);
 75     }
 76 }
 77 void Del(int k){
 78     mnS[k].erase(mnS[k].find(0));
 79     for(int i=k;fa[i];i=fa[i]){
 80         del(i);
 81         S[i].erase(S[i].find(DIST::dis(k,fa[i])));
 82         add(i);
 83     }
 84 }
 85 void update(int k){
 86     if (vis[k])Del(k);
 87     vis[k]^=1;
 88     if (vis[k])Add(k);
 89 }
 90 int query(int k){
 91     int ans=0x3f3f3f3f;
 92     if (mnS[k].size())ans=(*mnS[k].begin());
 93     for(int i=k;fa[i];i=fa[i]){
 94         del(i);
 95         if (!mnS[fa[i]].empty())ans=min(ans,(*mnS[fa[i]].begin())+DIST::dis(k,fa[i]));
 96         add(i);
 97     }
 98     if (ans==0x3f3f3f3f)ans=-1;
 99     return ans;
100 }
101 int main(){
102     scanf("%d",&n);
103     memset(head,-1,sizeof(head));
104     for(int i=1;i<n;i++){
105         scanf("%d%d",&x,&y);
106         add(x,y),add(y,x);
107     }
108     DIST::dfs(1,0,0);
109     DIVIDE::dfs(1);
110     scanf("%d",&m);
111     for(int i=1;i<=m;i++){
112         scanf("%d%d",&x,&y);
113         if (!x)update(y);
114         else printf("%d
",query(y));
115     }
116     return 0;
117 }
View Code

下面,来考虑LCT的做法:

与求最小生成树的LCT相同,将边拆成点,并将边权变为点权

为了方便,这里给出一些定义:

1.称一个点对应的实链为Splay上其子树内所有点(显然这些点构成一段实链)

2.称一个点的子树范围为自身$cup $其Splay上左右儿子的子树范围$cup $所有虚儿子的子数范围

(也可以理解为原树中对应的实链上所有节点虚儿子子树的并)

对每一个节点,维护以下信息:

1.对应的实链点权和(即Splay上子树点权和)

2.其子树范围内到其对应的实链链顶/链尾距离最小的白点(的距离)

3.其自身和每一个虚儿子的子树范围内到其距离最小的白点(的距离)所构成的集合(距离不包括其自身的权值,这是为了方便修改权值)

通过这些信息,只需要将查询点make_root到根,那么此时整个Splay的根(并不是原树的根)的第2个信息即为答案(但由于可能找不到该位置,不妨再splay(x)一下)

下面,问题即如何维护这些信息,实际上LCT的信息维护基本都只需要考虑以下两种变化:

1.修改了Splay上子树的信息(即重新up),那么其中第1个信息容易维护,第3个信息没有影响,下面来考虑如何维护第2个信息(以链顶为例)——

将其子树范围分为三类:

(1)Splay上左儿子的子树范围,这个即为左儿子的该信息

(2)Splay上右儿子的子树范围,考虑其对应的实链,即右儿子先走到自己对应的链链顶,再从其通过左儿子对应的整条实链即可,那么即右儿子的该信息+其点权+左儿子的第1个信息

(3)自身$cup $虚儿子的子树范围,同样要先到达其,即第3个信息维护的集合中最小值+左儿子的第1个信息

2.增加/删除了某个虚儿子(不维护子树信息的LCT对此无影响),此时即要求该虚儿子子树范围内到其距离最小的点(的距离),将虚儿子的第2个信息(链顶)+其点权在集合中加入或删除即可

综上,时间复杂度为$o(nlog^{2}n)$,可以通过

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define N 200005
  4 multiset<int>S[N];
  5 int n,m,x,y,vis[N],st[N],fa[N],tag[N],val[N],sum[N],ch[N][2],f[N][2];
  6 bool check(int k){
  7     return (ch[fa[k]][0]!=k)&&(ch[fa[k]][1]!=k);
  8 }
  9 int which(int k){
 10     return ch[fa[k]][1]==k;
 11 }
 12 void rev(int k){
 13     tag[k]^=1;
 14     swap(ch[k][0],ch[k][1]);
 15     swap(f[k][0],f[k][1]);
 16 }
 17 void add_vir(int k){
 18     S[fa[k]].insert(f[k][0]);
 19 }
 20 void del_vir(int k){
 21     S[fa[k]].erase(S[fa[k]].find(f[k][0]));
 22 }
 23 int get_min(int k){
 24     if (S[k].empty())return 0x3f3f3f3f;
 25     return (*S[k].begin());
 26 }
 27 void up(int k){
 28     sum[k]=sum[ch[k][0]]+sum[ch[k][1]]+val[k];
 29     f[k][0]=min(f[ch[k][0]][0],min(get_min(k),f[ch[k][1]][0])+val[k]+sum[ch[k][0]]);
 30     f[k][1]=min(f[ch[k][1]][1],min(get_min(k),f[ch[k][0]][1])+val[k]+sum[ch[k][1]]);
 31 }
 32 void down(int k){
 33     if (tag[k]){
 34         if (ch[k][0])rev(ch[k][0]);
 35         if (ch[k][1])rev(ch[k][1]);
 36         tag[k]=0;
 37     }
 38 }
 39 void rotate(int k){
 40     int f=fa[k],g=fa[f],p=which(k);
 41     fa[k]=g;
 42     if (!check(f))ch[g][which(f)]=k;
 43     fa[ch[k][p^1]]=f,ch[f][p]=ch[k][p^1];
 44     fa[f]=k,ch[k][p^1]=f;
 45     up(f),up(k);
 46 }
 47 void splay(int k){
 48     for(int i=k;;i=fa[i]){
 49         st[++st[0]]=i;
 50         if (check(i))break;
 51     }
 52     while (st[0])down(st[st[0]--]);
 53     for(int i=fa[k];!check(k);i=fa[k]){
 54         if (!check(i)){
 55             if (which(i)==which(k))rotate(i);
 56             else rotate(k);
 57         }
 58         rotate(k);
 59     }
 60 }
 61 void access(int k){
 62     int lst=0;
 63     while (k){
 64         splay(k);
 65         if (ch[k][1])add_vir(ch[k][1]);
 66         if (lst)del_vir(lst);
 67         ch[k][1]=lst,up(k);
 68         lst=k,k=fa[k];
 69     }
 70 }
 71 void make_root(int k){
 72     access(k);
 73     splay(k);
 74     rev(k);
 75 }
 76 void add(int x,int y){
 77     make_root(x);
 78     make_root(y);
 79     fa[y]=x,add_vir(y),up(x);
 80 }
 81 void upd_val(int k,int x){
 82     make_root(k);
 83     val[k]=x,up(k);
 84 }
 85 void upd_col(int k){
 86     make_root(k);
 87     if (vis[k])S[k].erase(S[k].find(0));
 88     vis[k]^=1;
 89     if (vis[k])S[k].insert(0);
 90     up(k);
 91 }
 92 int query(int k){
 93     make_root(k);
 94     if (f[k][0]==0x3f3f3f3f)return -1;
 95     return f[k][0];
 96 }
 97 int main(){
 98     scanf("%d",&n);
 99     memset(f,0x3f,sizeof(f));
100     for(int i=1;i<n;i++){
101         scanf("%d%d",&x,&y);
102         add(x,i+n),add(y,i+n);
103         upd_val(i+n,1);
104     }
105     scanf("%d",&m);
106     for(int i=1;i<=m;i++){
107         scanf("%d%d",&x,&y);
108         if (!x)upd_col(y);
109         else printf("%d
",query(y));
110     }
111     return 0;
112 }
View Code
原文地址:https://www.cnblogs.com/PYWBKTDA/p/15330813.html