【ADAFTBLL - Ada and Football】题解

Description

这题肯定是用树上带修莫队来做。

你很容易发现这和 糖果公园 一样,都是树上带修莫队。

Solution

所以就把树上莫队和带修莫队给合并起来就行了。

我猜你们一定已经学了带修莫队,这里就说一下树上莫队。

我们要先把原序列给变成欧拉序,把树上的点都变成序列,我们会得到一个长度为 \(2n\) 的序列,这就是欧拉序。

这样的话每个点肯定会被统计两次。

就像欧拉序是 12443321,每个点会统计两遍。

所以我们要判断是第一次统计还是第二次统计。

另外还有 \(LCA\) 的问题。

我们在预处理询问的时候,要求一遍 \(LCA\)

  • 如果 \(LCA\) 等于左右两端点的一个的时候,就说明这两个端点实在同一个子树内部,这时候就不用求 \(LCA\),明显两个链上没有 \(LCA\)

  • 如果 \(LCA\) 不等于任何一个的时候,就要特判 \(LCA\),加上 \(LCA\) 的贡献。

修改和普通带修改莫队一样,只要加一维时间轴即可。

代码里面有一些注释。

Code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int Maxk = 2e5 + 10;
int n,size,cnt,m,Q,cnts,cntq;
int st[Maxk],ed[Maxk],sum[Maxk];
int tot[Maxk],dfn[Maxk << 1],top[Maxk];
int deep[Maxk],fa[Maxk],used[Maxk];
int a[Maxk],son[Maxk],tmp[Maxk];
int head[Maxk],L[Maxk],R[Maxk],block[Maxk];
int ans[Maxk],Ans;
struct node{
  int from;
  int to;
  int next;
}e[Maxk << 1];
struct Query{
  int l;
  int r;
  int pos;
  int nert;
  int Lca_;
}s[Maxk];
struct Modify {
  int past;
  int Now;
}q[Maxk];
inline int read()
{
	int x = 0,f = 0;char ch = getchar();
	while(!isdigit(ch)) f |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48),ch = getchar();
	return f ? -x : x;
}
void add_edge(int from,int to)//连边 
{
  e[++ cnt].to = to;
  e[cnt].from = from;
  e[cnt].next = head[from];
  head[from] = cnt;
  return;
}

void BLOCK()//分块
{
  int nn = n << 1;
  int len = pow(nn,0.6666667);
  int T = nn / len;
  for(int i = 1;i <= T;i ++) {
    L[i] = (i - 1) * len + 1;
    R[i] = i * len; 
  }
  if(R[T] < nn) T ++,L[T] = R[T - 1] + 1,R[T] = nn;
  for(int i = 1;i <= T;i ++) 
    for(int j = L[i];j <= R[i];j ++)
      block[j] = i;
  return; 
}

void dfs1(int now,int f)//树链剖分常规操作
{
  deep[now] = deep[f] + 1;
  fa[now] = f;
  st[now] = ++ size;
  dfn[size] = now;
  tot[now] = 1;
  for(int i = head[now];i;i = e[i].next) {
    int y = e[i].to;
    if(y == f) continue;
    dfs1(y,now);
    tot[now] += tot[y];
    if(tot[y] > tot[son[now]]) son[now] = y;
  }
  ed[now] = ++size;
  dfn[size] = now;
}

void dfs2(int now,int topfa)
{
  top[now] = topfa;
  if(!son[now]) return ;
  dfs2(son[now],topfa);
  for(int i = head[now];i;i = e[i].next) {
    int y = e[i].to;
    if(y == fa[now] || y == son[now]) continue;
    dfs2(y,y);
  }
}

int LCA(int x,int y)//求出 LCA
{
  while(top[x] ^ top[y]) {
    if(deep[top[x]] < deep[top[y]]) swap(x,y);
    x = fa[top[x]];
  }
  return deep[x] < deep[y] ? x : y;
}

bool CMP(Query x, Query y) { return block[x.l] == block[y.l] ? (block[x.r] == block[y.r] ? x.nert < y.nert : x.r < y.r) : x.l < y.l; }//排序

void Prepare()
{
  for(int i = 1;i <= Q;i ++) {
    int op = read(),x = read() + 1,y = read() + 1;
    if(op == 1) {
      q[++ cntq].past = x;
      q[cntq].Now = y - 1;//修改操作
    }
    else {
      if(st[x] > st[y]) swap(x,y);
      int Lca = LCA(x,y);//求出 LCA
      s[++ cnts].pos = cnts;
      s[cnts].nert = cntq;
      if(Lca == x) {//同子树内
        s[cnts].l = st[x];
        s[cnts].r = st[y];
      }
      else {
        s[cnts].l = ed[x];//不同子树记得加上 LCA
        s[cnts].r = st[y];
        s[cnts].Lca_ = Lca;
      }
    }
  }
  return;
}

void del(int x)//删除操作
{
  Ans -= sum[x] * (sum[x] - 1) * 1LL / 2;
  sum[x] --;
  Ans += sum[x] * (sum[x] - 1) * 1LL / 2;
}
void add(int x)//添加操作
{
  Ans -= sum[x] * (sum[x] - 1) * 1LL / 2;
  sum[x] ++;
  Ans += sum[x] * (sum[x] - 1) * 1LL / 2;
}

void calc(int x)//判断应该删除这个元素还是加上
{
  used[x] ? del(a[x]) : add(a[x]);
  used[x] ^= 1;
}

void work(int now)//修改某个地方的值
{
  if(used[q[now].past]) {
    del(a[q[now].past]);
    add(q[now].Now);
  } 
  swap(a[q[now].past],q[now].Now);
}

void Solve()
{
  sort(s + 1,s + cnts + 1,CMP);
  int l = 1,r = 0,now = 0;
  for(int i = 1;i <= cnts;i ++) {
    int ql = s[i].l;
    int qr = s[i].r;
    while(l < ql) calc(dfn[l ++]);
    while(l > ql) calc(dfn[-- l]);
    while(r > qr) calc(dfn[r --]);
    while(r < qr) calc(dfn[++ r]);
    while(now < s[i].nert) work(++ now);
    while(now > s[i].nert) work(now --);
    if(s[i].Lca_) calc(s[i].Lca_);
    ans[s[i].pos] = Ans;
    if(s[i].Lca_) calc(s[i].Lca_);//加上 LCA 后记着还原
  }
  for(int i = 1;i <= cnts;i ++) printf("%lld\n",ans[i]);
  return;
}

signed main()
{
  n = read(),Q = read();
  for(int i = 1;i <= n;i ++) a[i] = read();
  for(int i = 1;i <= n - 1;i ++) {
    int x = read() + 1,y = read() + 1;//记着连边 + 1
    add_edge(x,y);
    add_edge(y,x);
  }
  BLOCK();
  dfs1(1,0);
  dfs2(1,1);
  Prepare();
  Solve();
  return 0;
}
posted @ 2021-04-14 20:51  Ti_Despairy  阅读(52)  评论(0)    收藏  举报