点分治&点分树


点分治

P3806 【模板】点分治1

题目让我们找是否存在距离为 $k$ 的点对是否存在。如果暴力统计每个点对之间的距离,时间显然会爆。

在我们暴力找的时候,是乱序的,所以我们首先要让它有序。首先,我们先要找到最小值,可以找树的重心,这样会快就能找到最短的边,然后将重心标记换一个近似于重心的点,找次短……但是这样的复杂度仍然暴力。

我们要找的是是否存在,所以完全可以在找到最短或其它的时候往两边扩展,查看是否会产生结果,我们找的最短,次短……只是一个遍历的顺序。

找到最大范围,通过不断比对大小,一点点往里缩,尝试匹配即可。

代码思路

  • 连边(读取基础信息)。

  • 存储询问(找到一个根节点后,根据每个询问尝试匹配每种情况)。

  • 找重心(处理子树的重量,判断重心)。

  • 标记重心标号(后边换重心是方方便确认是否已经遍历过)。

  • 二分,处理长度,尝试匹配,匹配成功标记。

  • 换重心,继续尝试。

  • 按标记,输出结果。

(每个节点只能记录当时没有被标记过当过重心的节点,所以每个节点的子树的重量(子树中的节点的个数)不同。)

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cctype>
using namespace std;
long long r_r(){//快读 
  long long x=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    x=(x<<1)+(x<<3)+(c^48);
    c=getchar();
  }
  return x*f;
}
const int o_o=1e6+10;
int n,m;
int q_i[o_o];//存储询问 

int m_s[o_o];
//存储最大树(最大子树或除了最大子树之外的部分) 
//如果两者相差很小,说明以它为根节点更平衡 

int s_z[o_o];//存储子树大小 
int g_g;//根节点(重心) 
int x_a;//临时数组的下标 
int d_p[o_o];//深度(到根节点权值) 
int b_i[o_o];//记录当前节点从哪个节点来的 
int a_i[o_o];//临时数组 
bool b_p[o_o];//判断是否标记过 
bool b_b[o_o];//统计结果 
struct po{//链式前向星 
  int v;
  int n_t;
  int v_l;
}p_p[o_o<<1];
int h_d[o_o],x_p;
void a_d(int u,int v,int w){//加边 
  x_p++;
  p_p[x_p].n_t=h_d[u];
  p_p[x_p].v=v;
  p_p[x_p].v_l=w;
  h_d[u]=x_p;
}
void g_t(int k,int f,int v_l){
  s_z[k]=1;//初始化节点大小 
  m_s[k]=0;//初始化节点最大子树 
  for(int i=h_d[k];i;i=p_p[i].n_t){//跑边 
    int v=p_p[i].v;
    if(v==f||b_p[v])continue;//是父节点或已经当过根节点了 
    g_t(v,k,v_l);//继续找 
    s_z[k]+=s_z[v];//累计子树大小 
    m_s[k]=max(s_z[v],m_s[k]);//找最大子树 
  }
  m_s[k]=max(m_s[k],v_l-s_z[k]);//选择子树或除了子树 
  
  if(!g_g||m_s[k]<m_s[g_g])g_g=k;
  //根节点不存在或者比根节点的最大树小,更新根节点 
}
bool cmp(int x,int y){//判断深度,保证从小到大 
  return d_p[x]<d_p[y];
}
void g_l(int k,int f,int l_n,int f_m){
  a_i[++x_a]=k;//读取编号 
  d_p[k]=l_n;//记录深度 
  b_i[k]=f_m;//记录来的节点(从那个到根节点) 
  for(int i=h_d[k];i;i=p_p[i].n_t){
    int v=p_p[i].v;
    if(v==f||b_p[v])continue;
    g_l(v,k,l_n+p_p[i].v_l,f_m);//注意深度累计
    
    //此处的深度就是价值(边权) 
  }
}
void c_c(int k){
  x_a=0;//初始化临时数组下表 
  a_i[++x_a]=k;//读入当前标号 
  d_p[k]=0;//初始化根节点的深度 
  b_i[k]=k;//初始化当前节点到当前节点 
  for(int i=h_d[k];i;i=p_p[i].n_t){//枚举边 
    int v=p_p[i].v;
    if(b_p[v])continue;
    g_l(v,k,p_p[i].v_l,v);
    //处理所有节点的基础信息 
  }
  sort(a_i+1,a_i+x_a+1,cmp);//将目前的节点按深度从小到大排序(方便二分) 
  for(int i=1;i<=m;i++){//处理每个询问 
    int l=1,r=x_a;//初始化左右边界 
    if(b_b[i])continue;//已经匹配成功 
    while(l<r){//暴力找左右长度 
      if(d_p[a_i[l]]+d_p[a_i[r]]>q_i[i])r--;//比目标大,右边回缩 
      else if(d_p[a_i[l]]+d_p[a_i[r]]<q_i[i])l++;//比目标小,左边回缩 
      else if(b_i[a_i[l]]==b_i[a_i[r]]){//来的点相同(根节点做左右) 
        if(d_p[a_i[r]]==d_p[a_i[r-1]])r--;//如果权值相同,右边回缩 
        else l++;//否则左边回缩 
      }else{
        b_b[i]=1;//标记,匹配成功 
        break;
      }
    }
  }
}
void s_v(int k){//处理根节点 
  b_p[k]=1;//标记 
  c_c(k);
  for(int i=h_d[k];i;i=p_p[i].n_t){//遍历每个边 
    int v=p_p[i].v;//通向的边 
    if(b_p[v])continue;
    g_g=0;//根节点初始化 
    
    g_t(v,0,s_z[v]);
    //处理新的重心,在真正重心直接子叶(近似中心)的子树寻找新的重心。  
    
    s_v(g_g);//继续处理 
  }
}
int main(){
  n=r_r(),m=r_r();
  for(int i=1;i<=n-1;i++){//加边 
    int u=r_r(),v=r_r(),w=r_r();
    a_d(u,v,w);
    a_d(v,u,w);
  }
  for(int i=1;i<=m;i++){
    q_i[i]=r_r();//存储询问 
    if(!q_i[i])b_b[i]=1;//标记询问为 0 的询问 
  }
  m_s[0]=n;//初始化 
  g_t(1,0,n);//确定根节点 
  s_v(g_g);//处理根节点 
  for(int i=1;i<=m;i++){//输出结果 
    if(b_b[i])puts("AYE");
    else puts("NAY");
  }
  return 0;
}

点分树

传送门

题目要我们求点覆盖的权值和单点修改。

和点分治的思路相同的是要找重心,要存节点来源(下面叫做父节点),在上面的题中我们确定的是长度是否存在。但是本题我们求的是范围点值和。

不难发现,我们是根据离点的距离确定范围,那么我们就可以通过存储每个深度(到当前节点的距离)来处理权值和(这里我们用的是 树状数组)。

同时我们要存储当前节点的父节点的深度的权值信息,这样我们就能在从当前节点往回爬的时候更方便的去重。

代码思路

  • 初始化并读取基本信息(节点权值和连边)。

  • 找重心。

  • 处理所有节点到重心的距离,深度贡献的权值。统计处理父节点相关情况。

  • 找新重心,继续处理上边的步骤。

  • 覆盖权值询问。

  • 统计当前节点的贡献。

  • 跳父节点们继续处理贡献同时去重(有些权值贡献多次),结束条件是父节点到节点的距离在覆盖范围内。

  • 输出统计结果,同时记录结果(信息解密要用)。

  • 单点修改。

  • 从当前节点往根节点跳(只对这些点有贡献),树状数组单点修改,接的同时处理记录父节点情况。

  • 城市权值修改。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cctype>
#include<map>
#include<vector>
using namespace std;
long long r_r(){//快读 
  long long x=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    x=(x<<1)+(x<<3)+(c^48);
    c=getchar();
  }
  return x*f;
}
const int o_o=2e5+5;
int l_t(int x){
  return x&(-x);
}
int n,m;
int a_s;//结果 
int l_a;//上次输出,用于解密 
int f[o_o];
int d[o_o];
int b_b[o_o];//标记节点是否当过重心 
int d_p[o_o];//记录当前重心下每个节点的深度 
int m_d_p;//记录当前重心形成的树的最大深度 

//链式前向星存边 
struct po{
  int v;
  int n_t;
}p_p[o_o<<1];//存边 
int x_p,h_d[o_o];

int v_v[o_o];//每个节点(城市)的权值 
map<int,int>i_d[o_o];//记录每个节点在不同重心时的深度 
vector<int>l_s[o_o];//当前重心时上次重心的区间和 
vector<int>n_s[o_o];//当前重心时的区间权值和 
void a_d(int u,int v){//连边 
  p_p[++x_p].v=v;
  p_p[x_p].n_t=h_d[u];
  h_d[u]=x_p;
}
int g_g;//根节点(重心) 
int s_n;
int m_s[o_o];//当前节点最大子树 
int s_z[o_o];//子树大小 
void d_f(int x,int f_a){//找根节点 
  s_z[x]=1;//初始化子树大小 
  m_s[x]=0;//初始化当前节点最大子树 
  for(int i=h_d[x];i;i=p_p[i].n_t){//遍历边 
    if(p_p[i].v!=f_a&&!b_b[p_p[i].v]){
      d_f(p_p[i].v,x);
      s_z[x]+=s_z[p_p[i].v];//累计子树大小 
      m_s[x]=max(m_s[x],s_z[p_p[i].v]);//更新最大子树 
    }
  }
  m_s[x]=max(m_s[x],s_n-s_z[x]);
  if(m_s[x]<m_s[g_g])g_g=x;//更新根节点 
}
void a_d(int k_k,int x,int v){
  int t=n_s[k_k].size();//当前的深度范围 
  while(x<t){//在范围内 
    n_s[k_k][x]+=v;//累计区间深度权值 
    x+=l_t(x);
  }
}
void a_d2(int k_k,int x,int v){
  int t=l_s[k_k].size();//上个根节点(重心)的范围 
  while(x<t){
    l_s[k_k][x]+=v;//累计区间深度权值 
    x+=l_t(x);
  }
}
int q_1(int k_k,int x){
  int r_s=0;
  if(x>=n_s[k_k].size())
    x=(int)n_s[k_k].size()-1;
  //限制区间范围(深度只有这些,范围再大也没用) 
  
  while(x){
    r_s+=n_s[k_k][x];//统计目标深度范围内的权值 
    x-=l_t(x);
  }
  return r_s;
}
int q_2(int k_k,int x){
  int r_s=0;
  if(x>=l_s[k_k].size())
    x=(int)l_s[k_k].size()-1;
  //限制区间范围(深度只有这些,范围再大也没用) 
  
  while(x){
    r_s+=l_s[k_k][x];//统计目标深度范围内的权值 
    x-=l_t(x);
  }
  return r_s;
}
void c_1(int x,int f_a,int k_k){
  s_z[x]=1;//初始化子树大小 
  d_p[x]=d_p[f_a]+1;//更新深度 
  i_d[k_k][x]=d_p[x];//记录深度 
  for(int i=h_d[x];i;i=p_p[i].n_t)//遍历所有相连的点 
    if(!b_b[p_p[i].v]&&p_p[i].v!=f_a){//没有当过重心并且不是父节点 
      c_1(p_p[i].v,x,k_k);//先遍历子节点 
      s_z[x]+=s_z[p_p[i].v];//更新子树大小 
    }
  m_d_p=max(d_p[x],m_d_p);//更新最大深度 
}
void c_2(int x,int f_a,int k_k,int k){
  a_d(k_k,d_p[x],v_v[x]);
  //当 k_k 为根节点时,x 节点的深度和权值传参 
  
  if(k)a_d2(k_k,i_d[k][x],v_v[x]);
  //不是初始点,当 k_k 为根节点时,x节点在上一个重心的深度和权值 
   
  for(int i=h_d[x];i;i=p_p[i].n_t)//遍历所有相连的点 
    if(!b_b[p_p[i].v]&&p_p[i].v!=f_a)//没当过重心并且不是父节点 
      c_2(p_p[i].v,x,k_k,k);//继续遍历 
}
void f_t(int x,int f_a,int h){
  int m_x;
  b_b[x]=1;//标记,当前节点当过重心了 
  m_d_p=0;//初始化最大深度 
  c_1(x,0,x);//处理每个节点深度和子树大小 
  for(int i=0;i<=m_d_p;++i)n_s[x].push_back(0);//初始化 
  for(int i=0;i<=h;++i)l_s[x].push_back(0);//初始化 
  m_x=m_d_p;//记录最大深度 
  c_2(x,0,x,f_a);//维护所有节点在当前重心和上一个重心时的区间和 
  for(int i=h_d[x];i;i=p_p[i].n_t){//遍历所有相连的点 
    if(b_b[p_p[i].v])continue;
    s_n=s_z[p_p[i].v];//更新节点数量 
    
    //初始化(找别的重心) 
    g_g=0;
    m_s[0]=0x3f3f3f3f;
    d_f(p_p[i].v,x);//初始化新重心 
    f[g_g]=x;//更新父节点 
    f_t(g_g,x,m_x);//继续处理 
  }
}
int main(){
  n=r_r(),m=r_r();
  for(int i=1;i<=n;++i)v_v[i]=r_r();//读入每个城市的价值 
  for(int i=1;i<n;++i){//连边 
    int u=r_r(),v=r_r();
    a_d(u,v);
    a_d(v,u);
  }
  //初始化 
  g_g=0;
  m_s[0]=0x3f3f3f3f;
  s_n=n;//记录节点总数量 
  d_f(1,0);//找重心 
  f_t(g_g,0,0);//处理所有节点成为重心的情况 
  while(m--){//处理询问 
    int op=r_r(),x=r_r()^l_a,v=r_r()^l_a;//解密 
    if(!op){//统计权值 
      int f_x=f[x];//记录父节点 
      int l_x=x;//记录当前节点 
      int f_d;//记录深度 
      a_s+=q_1(x,v+1);//记录当前节点范围内的权值 
      while(f_x){//没有到根就一直爬 
        f_d=i_d[f_x][x]-1;//记录深度 
        if(v-f_d<0){//不在影响范围内 
          l_x=f_x;//记录这次节点 
          f_x=f[f_x];//往上爬 
          continue;
        }
        a_s+=q_1(f_x,v-f_d+1);//统计贡献 
        a_s-=q_2(l_x,v-f_d+1);//去重 
        l_x=f_x;//记录节点 
        f_x=f[f_x];//往上爬 
      }
      printf("%d\n",(l_a=a_s)); 
      a_s=0;
    }else{//单点修改 
      int k_k=x;//记录节点 
      a_d(x,1,-v_v[x]+v);//当前节点区间修改 
      while(f[x]){//没有到根就一直爬
        int f_d=i_d[f[x]][k_k];//记录深度 
        
        //更新区间权值 
        a_d(f[x],f_d,-v_v[k_k]+v);
        a_d2(x,f_d,-v_v[k_k]+v);
        
        x=f[x];//往上爬 
      }
      v_v[k_k]=v;//节点权值更新 
    }
  }
  return 0; 
}

优化

这份代码不仅慢,空间也大,但可以过。。。

但是,可以优化。

时间空间优化

前置知识:树链剖分

在上面代码中,我们直接用一个二维数组(map 也占一维)将每个节点在不同重心时的深度记录下来了,但是由于本题的数据范围不是很大,所以 $O(\log n)$ 和一个大常数差不多,不会过多影响时间效率,但是大大节省了空间。

我们将整颗树剖分一遍,首先我们用树剖是解决快速求出每个节点在不同重心时的深度,就是当前节点在不同重心的距离,那么我们就可以通过树剖求两点的公共祖先,从而快速获得长度。

同时,上面为了防止浪费空间我们用的 map 存储。但是它的内部是红黑树,每多一个值都要插入,还要调整整棵树(unordered_map 效率更高,但是也很慢)。

所以,我们用树剖求 lca 实现了时空双赢!

优化代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cctype>
#include<map>
#include<vector>
using namespace std;
long long r_r(){//快读 
  long long x=0,f=1;
  char c=getchar();
  while(!isdigit(c)){
    if(c=='-')f=-1;
    c=getchar();
  }
  while(isdigit(c)){
    x=(x<<1)+(x<<3)+(c^48);
    c=getchar();
  }
  return x*f;
}
const int o_o=1e5+10;
int v_v[o_o<<1],h_d[o_o],n_t[o_o<<1],x_p;//链式前向星 
void a_d(int u,int v){//加边 
  v_v[++x_p]=v;
  n_t[x_p]=h_d[u];
  h_d[u]=x_p;
}
bool b_b[o_o];//判断是否当过根节点 
int s_z[o_o];//子树总量(子树节点个数) 
int m_s[o_o];//最大子树 
int s_n;//节点数量 
int g_g;//根节点(当前重心) 
void f_g(int x,int f_a){//处理重心 
  s_z[x]=1;//初始化子树重量 
  m_s[x]=0;//初始化最大子树 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v]&&v!=f_a){//没有当过重心不是父节点 
      f_g(v,x);
      s_z[x]+=s_z[v];//累计子树重量 
      m_s[x]=max(m_s[x],s_z[v]);//更新最大子树 
    }
  }
  m_s[x]=max(m_s[x],s_n-s_z[x]);
  if(m_s[x]<m_s[g_g])g_g=x;//更新根节点(重心) 
}
struct ss{
  vector<int> v_l;
  int l_n;
  ss(){}
  ss(int l_i){//初始化长度 
    l_n=l_i+1;
    v_l.resize(l_i+2);
  }
  void g_a(int k,int v){//树状数组更新权值 
    for(int i=k+1;i<=l_n;i+=i&(-i))v_l[i]+=v;
  }
  int q_i(int k){//树状数组统计权值 
    int r_s=0;
    for(int i=min(k+1,l_n);i>0;i-=i&(-i))r_s+=v_l[i];//注意边界 
    return r_s;
  }
}n_x[o_o],l_x[o_o];
int i_d[o_o];//子树中节点编号 
int n_i;//子树中节点的数量 
int d_p[o_o];//节点深度 
void s_l(int x,int f_a){//记录子树中所有节点 
  i_d[++n_i]=x;//进入队中 
  d_p[x]=d_p[f_a]+1;//更新深度 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v]&&v!=f_a)s_l(v,x);//在子树中并且不是父节点继续找 
  }
}
int v_l[o_o];//节点权值 
int l_i[o_o],r_i[o_o];//记录左右端点 
int m_p[o_o];//当前节点的深度 
int g_i[o_o];//记录节点的所属的根节点 
void b_t(int k){//处理当前根节点整棵树的节点的深度,管辖范围深度对应的权值 
  n_i=0;//队列下标重置 
  d_p[k]=0;//初始化深度 
  int m_d_p=0;//初始化最大深度 
  for(int i=h_d[k];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v]){//在子树范围内 
      l_i[v]=n_i+1;//记录左端点 
      s_l(v,k);
      r_i[v]=n_i;//记录右端点 
      m_p[v]=0;//初始化节点深度 
      for(int j=l_i[v];j<=n_i;j++)//范围内最大深度节点 
        m_p[v]=max(m_p[v],d_p[i_d[j]]);//当前节点子树中最大深度更新 
      m_d_p=max(m_d_p,m_p[v]);//更新总子树最大深度 
    }
  }
  
  //处理当前节点信息 
  n_x[k]=ss(m_d_p);//初始化长度 
  n_x[k].g_a(0,v_l[k]);//树状数组统计区间权值 
  for(int i=h_d[k];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v]){//子树范围内 
      int g_g=g_i[v];//记录根节点 
      l_x[g_g]=ss(m_p[v]);//更新子树长度 
      for(int j=l_i[v];j<=r_i[v];++j){//枚举子树元素 
        int x=i_d[j];//记录当前下表 
        
        //根据深度更新权值 
        n_x[k].g_a(d_p[x],v_l[x]);//更新当前节点权值 
        l_x[g_g].g_a(d_p[x],v_l[x]);//更新子树节点权值 
      }
    }
  }
}
int f_a[o_o];//记录父节点 
void d_f(int x){
  b_b[x]=1;//标记节点当过重心 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v]){//在子树范围内(没当过重心) 
      s_n=s_z[v];//更新子树中节点数量 
      
      //更新重心 
      g_g=0;
      f_g(v,0);
      g_i[v]=g_g;//记录子树所属的根节点 
      f_a[g_g]=x;//记录父节点 
    }
  }
  b_t(x);//处理整棵树的相关信息 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(!b_b[v])d_f(g_i[v]);//处理所有子节点 
  }
}

//树链剖分部分 
int f_i[o_o];//父节点 
int b_s[o_o];//字数最大子节点更新 
int t_p[o_o];//记录链顶 
void d_1(int x,int f_a){
  s_z[x]=1;//初始化子树节点大小 
  f_i[x]=f_a;//更新父节点 
  d_p[x]=d_p[f_a]+1;//更新深度 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(v!=f_a){//不是父节点(不会跑环) 
      d_1(v,x);
      s_z[x]+=s_z[v];//累计子树大小 
      if(s_z[v]>s_z[b_s[x]])b_s[x]=v;//更新子树最大的子节点 
    }
  }
}
void d_2(int x,int t_n){
  t_p[x]=t_n;//记录链顶 
  if(!b_s[x])return; 
  d_2(b_s[x],t_n);//继续跑链 
  for(int i=h_d[x];i;i=n_t[i]){
    int v=v_v[i];
    if(v!=f_i[x]&&v!=b_s[x])d_2(v,v);//开新链 
  }
}
int l_a(int x,int y){//找公共祖先 
  while(t_p[x]!=t_p[y]){//跳到一条链上 
    if(d_p[t_p[x]]<d_p[t_p[y]])swap(x,y);
    x=f_i[t_p[x]];//往回爬 
  }
  return d_p[x]<d_p[y]?x:y;//节点小的时公共祖先 
}

int g_d(int x,int y){//计算距离 
  return d_p[x]+d_p[y]-(d_p[l_a(x,y)]<<1);
}
int q_i(int x,int k){//查询 
  int r_s=n_x[x].q_i(k);//记录权值和 
  int k_k=x;
  while(f_a[k_k]){
    int d=g_d(f_a[k_k],x);//计算距离 
    
    if(d<=k)r_s+=n_x[f_a[k_k]].q_i(k-d)-l_x[k_k].q_i(k-d);
    //统计路径上去重后的结果(注意在范围内的节点权值才统计) 
    
    k_k=f_a[k_k];//往回爬  
  }
  return r_s;
}
void g_a(int x,int v){//单点修改 
  int k_k=v-v_l[x];//变化的值 
  if(!k_k)return;//没有变化不用改 
  v_l[x]=v;//改权值 
  n_x[x].g_a(0,k_k);//子树树状数组范围权值更新 
  int x_x=x;
  while(f_a[x_x]){
    int d=g_d(f_a[x_x],x);//计算距离 
    n_x[f_a[x_x]].g_a(d,k_k);//更新父节点范围权值和 
    l_x[x_x].g_a(d,k_k);//更新子树范围权值和 
    x_x=f_a[x_x];//往根处爬 
  }
}
int main(){
  int n=r_r(),m=r_r();
  for(int i=1;i<=n;++i)v_l[i]=r_r();//记录节点权值 
  for(int i=1;i<=n-1;++i){//加边 
    int u=r_r(),v=r_r();
    a_d(u,v);
    a_d(v,u);
  }
  m_s[0]=n+1;//初始化 
  s_n=n;//记录节点数量 
  f_g(1,0);//处理重心 
  d_f(g_g);//处理整个树的基本信息 
  
  //将整棵树剖分 
  d_1(1,0);
  d_2(1,1);
  
  int l_s=0;//记录上次结果,用于解密 
  while(m--){
    int op=r_r(),x=r_r()^l_s,k=r_r()^l_s;//解密 
    if(op==0)printf("%lld\n",l_s=q_i(x,k));//询问范围内权值和 
    else g_a(x,k);//单点修改 
  }
  return 0;
}

文章作者: 王大神——A001
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王大神——A001 !
  目录