莫队——三种题型

普通莫队

P3901 数列找不同

Thinking

一定是用可以用莫队来写题,这点是不用质疑的,所以那就简单了,只需要判断每次询问的区间是否满足 r − l + 1 = = n u m r - l + 1 == num rl+1==num就行了。

C o d i n g 1 Coding_1 Coding1

莫队写法

#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

const int N = 1e6 + 10;

int n, m, a[N], num[N], ans[N], block, sum, k;

struct Node {
  int l, r, id;
  // bool operator < (const Node & t) const {
  //   return r < t.r;
  // }
}query[N];

bool cmp1(Node x, Node y) {
  return ((x.l / block) == (y.l / block)) ? x.r < y.r : x.l < y.l;
}//按照块排序

bool cmp2(Node x, Node y) {
  return ((x.l / block) != (y.l / block)) ? x.l < y.l : ((x.l / block) & 1) ? x.r < y.r : x.r > y.r;
}//按照块的奇偶排序

void add(int x) {
  num[a[x]]++;
  if(num[a[x]] == 1)  sum++;
  // sum += 2 * num[a[x]] - 1;
}

void del(int x) {
  num[a[x]]--;
  if(num[a[x]] == 0)  sum--;
  // sum -= 2 * num[a[x]] + 1;
}


int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read();
  for(int i = 1; i <= n; i++) a[i] = read();
  block = sqrt(n);
  for(int i = 1; i <= m; i++) {
    query[i].l = read(), query[i].r = read();
    query[i].id = i;
  }
  sort(query + 1, query + 1 + m, cmp2);
  int l = 0, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r > query[i].r) del(r--);
    while(r < query[i].r) add(++r);
    while(l < query[i].l) del(l++);
    while(l > query[i].l) add(--l);
    ans[query[i].id] = (sum == query[i].r - query[i].l + 1);
  }
  for(int i = 1; i <= m; i++)
    puts(ans[i] ? "Yes" : "No");
  return 0;
}

C o d i n g 2 Coding_2 Coding2

树状数组写法。

#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

const int N = 1e6 + 10;

int n, m, a[N], pos[N], tree[N], ans[N];

struct Node {
  int l, r, id;
  bool operator < (const Node & t) const {
    return r < t.r;
  }
}query[N];

void add(int x, int value) {
  while(x < N) {
    tree[x] += value;
    x += x & (-x);
  }
}

int ask(int x) {
  int ans = 0;
  while(x) {
    ans += tree[x];
    x -= x & (-x);
  }
  return ans;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read();
  for(int i = 1; i <= n; i++) a[i] = read();
  for(int i = 1; i <= m; i++) {
    query[i].l = read(), query[i].r = read();
    query[i].id = i;
  }
  sort(query + 1, query + 1 + m);
  int l = 1;
  for(int i = 1; i <= m; i++) {
    for(int j = l; j <= query[i].r; j++) {
      if(pos[a[j]])
        add(pos[a[j]], -1);
      pos[a[j]] = j;
      add(j, 1);
    }
    l = query[i].r + 1;
    ans[query[i].id] = (ask(query[i].r) - ask(query[i].l - 1) == query[i].r - query[i].l + 1);
  }
  for(int i = 1; i <= m; i++)
    puts(ans[i] ? "Yes" : "No");
  return 0;
}

D. Tree and Queries

Thinking

利用 d f s dfs dfs序的特性,同一颗子树上的节点会连成一片,所以很好的将一个树上问题转换成了区间问题,这个时候就可以用上莫队来写了。

Coding

#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

const int N = 1e5 + 10;

int head[N], to[N << 1], nex[N << 1], cnt = 1;
int a[N], n, m, block, sum[N], ans[N], Ans[N];
int pre[N], suc[N], rk[N], num;

struct Node {
  int l, r, id, minn;
  Node(int _l = 0, int _r = 0, int _id = 0, int _minn = 0) :
  l(_l), r(_r), id(_id), minn(_minn) {}
}ask[N];

bool cmp(Node a, Node b) {
  return ((a.l / block) != (b.l / block)) ? a.l < b.l : ((a.l / block) & 1) ? a.r < b.r : a.r > b.r;
}

void add_edge(int x, int y) {
  to[cnt] = y;
  nex[cnt] = head[x];
  head[x] = cnt++;
}

void dfs(int rt, int f) {
  pre[rt] = ++num;
  rk[num] = rt;
  for(int i = head[rt]; i; i = nex[i]) {
    if(to[i] == f)  continue;
    dfs(to[i], rt);
  }
  suc[rt] = num;
}

void add(int x) {
  Ans[++sum[a[x]]]++;
}

void del(int x) {
  Ans[sum[a[x]]--]--;
}

int main() {
  // freopen("in.txt", "r", stdin); 
  // freopen("out.txt", "r", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read();
  block = sqrt(n);
  for(int i = 1; i <= n; i++)  a[i] = read();
  for(int i = 1; i < n; i++) {
    int x = read(), y = read();
    add_edge(x, y);
    add_edge(y, x);
  }
  dfs(1, 0);
  for(int i = 1; i <= m; i++) {
    int x = read(), y = read();
    ask[i] = Node(pre[x], suc[x], i, y);
  }
  sort(ask + 1, ask + 1 + m, cmp);
  int l = 0, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r < ask[i].r) add(rk[++r]);
    while(r > ask[i].r) del(rk[r--]);
    while(l > ask[i].l) add(rk[--l]);
    while(l < ask[i].l) del(rk[l++]);
    ans[ask[i].id] = Ans[ask[i].minn];
  }
  for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
  return 0;
}

D. Powerful array

Thinking

同样是一个区间问题,我们考虑用莫队如何维护。

假设当前数字 x x x出现的次数是 n n n次,有 s u m 1 = n 2 x sum_1 = n ^ {2} x sum1=n2x

我们假设其增加一则变成了 s u m 2 = ( n + 1 ) 2 x = n 2 x + ( 2 n + 1 ) x sum_2 = (n + 1) ^ {2} x = n ^ {2} x + (2 n + 1)x sum2=(n+1)2x=n2x+(2n+1)x,其增量是 ( n < < 1 ∣ 1 ) x (n << 1 | 1)x (n<<11)x

我们再假设其减一则变成了 s u m 3 = ( n − 1 ) 2 x = n 2 x − ( 2 ( n − 1 ) + 1 ) x sum_3 = (n - 1) ^ {2} x = n ^ {2}x - (2(n - 1) + 1)x sum3=(n1)2x=n2x(2(n1)+1)x,其减少量是 ( ( n − 1 ) < < 1 ∣ 1 ) x ((n - 1) << 1 | 1)x ((n1)<<11)x

由此我们就推出了答案变换的公式了。

Coding

/*
  Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

const int N = 1e6 + 10;

int n, m, block, a[N];
ll num[N], ans[N], now;

struct Node {
  int l, r, id;
  bool operator < (const Node & t) const {
    return (l / block) != (t.l / block) ? l < t.l : ((l / block) & 1) ? r < t.r : r > t.r;
  }
}ask[N];

void add(int x) {
  now += (num[a[x]]++ << 1 | 1) * a[x];
}
void del(int x) {
  now -= (--num[a[x]] << 1 | 1) * a[x];
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "r", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read();
  for(int i = 1; i <= n; i++) a[i] = read();
  block = sqrt(n);
  for(int i = 1; i <= m; i++) {
    ask[i].l = read(), ask[i].r = read();
    ask[i].id = i;
  }
  sort(ask + 1, ask + 1 + m);
  int l = 1, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r < ask[i].r) add(++r);
    while(r > ask[i].r) del(r--);
    while(l < ask[i].l) del(l++);
    while(l > ask[i].l) add(--l);
    ans[ask[i].id] = now;
  }
  for(int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
  return 0;
}

P1533 可怜的狗狗

Thinking

权值线段树加莫队。

题目给的数据表意不明,所以我们最好还是给 a i a_i ai离散化后,再进行权值的插入删除操作。

这里的权值标记的是第 i i i只够是否在区间里,如果在就标记为 1 1 1,否则就删去这个点变成 0 0 0,对每个区间都完成了相应的操作,接下来我们就是要去查询第 K K K大的是什么,这里通过树状数组的前缀和性质对其进行二分,得到第 k k k大的 i d id id,然后通过这个得到答案。

Coding

/*
  Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}


const int N = 3e5 + 10;

int a[N], b[N], id[N], n, m, block;
int tree[N], ans[N];

struct Node {
  int l, r, k, id;

  Node(int _l = 0, int _r = 0, int _k = 0, int _id = 0) : l(_l), r(_r), k(_k), id(_id) {}

  void input() {
    l = read(), r = read(), k = read();
  }

  bool operator < (const Node & t) const {
    return (l / block) != (t.l / block) ? l < t.l : ((l / block) & 1) ? r < t.r : r > t.r;
  }
}ask[N];

void update(int x, int value) {
  while(x <= n) {
    tree[x] += value;
    x += x & (-x);
  }
}

int query(int x) {
  int ans = 0;
  while(x) {
    ans += tree[x];
    x -= x & (-x);
  }
  return ans;
}

int find(int value) {
  int l = 1, r = n;
  while(l < r) {
    int mid = l + r >> 1;
    if(query(mid) < value) l = mid + 1;
    else r = mid;
  }
  return l;
}

void del(int x) {
  if(id[x]) update(id[x], -1);//if(id[x])是为了放置树状数组死循环而加上了的。
}

void add(int x) {
  if(id[x]) update(id[x], 1);
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read(), block = sqrt(n);
  for(int i = 1; i <= n; i++) a[i] = b[i] = read();
  sort(b + 1, b + 1 + n);
  for(int i = 1; i <= n; i++) id[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;
  for(int i = 1; i <= m; i++) {
    ask[i].input();
    ask[i].id = i;
  }
  sort(ask + 1, ask + 1 + m);
  int l = 0, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r > ask[i].r) del(r--);
    while(r < ask[i].r) add(++r);
    while(l < ask[i].l) del(l++);
    while(l > ask[i].l) add(--l);
    ans[ask[i].id] = find(ask[i].k);
  }
  for(int i = 1; i <= m; i++) printf("%d\n", b[ans[i]]);
  return 0;
}

Chika and Friendly Pairs

Thinking

这题与上一题的思想有异曲同工之处,离散化后莫队,通过插入,删除,区间查询操作,来得到我们最后的答案。

Coding

/*
  Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

void print(ll x) {
  if(x < 10) {
    putchar(x + 48);
    return ;
  }
  print(x / 10);
  putchar(x % 10 + 48);
}

const int N = 3e4 + 10;

int a[N], b[N], tree[N], n, m, k, block, all;
ll sum, ans[N];

struct Node {
  int l, r, id;
  bool operator < (const Node & t) const {
    return (l / block) != (t.l / block) ? l < t.l : ((l / block) & 1) ? r < t.r : r > t.r; 
  }
}ask[N];

void update(int x, int value) {
  while(x <= n) {
    tree[x] += value;
    x += x & (-x);
  }
}

int query(int x) {
  int ans = 0;
  while(x) {
    ans += tree[x];
    x -= x & (-x);
  }
  return ans;
}

void del(int x) {//删除点,以及对答案进行的影响操作。
    			//我们不难发现出了这个点外在区间[b[x] - k, b[x] + k]里的点都会对答案进行贡献影响。
    			//所以我们删除这个点后查询query(r - 1) - query(l - 1)就是对答案的减小影响。
  update(x, -1);
  int l = lower_bound(b + 1, b + 1 + all, b[x] - k) - b;//这个点应该不用多说。
  int r = upper_bound(b + 1, b + 1 + all, b[x] + k) - b;//r是第一个大于b[x] + k的,所以r - 1是最后一个 <= b[x] + k的点。
  sum -= query(r - 1) - query(l - 1);
}

void add(int x) {
  update(x, 1);
  int l = lower_bound(b + 1, b + 1 + all, b[x] - k) - b;
  int r = upper_bound(b + 1, b + 1 + all, b[x] + k) - b;
  sum += query(r - 1) - query(l - 1) - 1;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read(), k = read(), block = sqrt(n);
  for(int i = 1; i <= n; i++) a[i] = b[i] = read();
  sort(b + 1, b + 1 + n);
  all = unique(b + 1, b + 1 + n) - (b + 1);
  for(int i = 1; i <=  n; i++)  a[i] = lower_bound(b + 1, b + 1 + all, a[i]) - b;
  for(int i = 1; i <= m; i++) {
    ask[i].l = read(), ask[i].r = read();
    ask[i].id = i;
  }
  sort(ask + 1, ask + 1 + m);
  int l = 1, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r > ask[i].r) del(a[r--]);
    while(r < ask[i].r) add(a[++r]);
    while(l > ask[i].l) add(a[--l]);
    while(l < ask[i].l) del(a[l++]);
    ans[ask[i].id] = sum;
  }
  for(int i = 1; i <= m; i++) print(ans[i]), putchar('\n');
  return 0;
}

可修改莫队

P1903 [国家集训队]数颜色 / 维护队列

Thinking

无非就是加了一个时间标记变成了有三个条件束缚,分别对 l , r , t l, r, t l,r,t三个变量进行排序,接下来就是莫队的基本操作了。

Coding

/*
  Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

void print(int x) {
  if(x < 10) {
    putchar(x + 48);
    return ;
  }
  print(x / 10);
  putchar(x % 10 + 48);
}

const int N = 1e6 + 10;

int a[N], num[N], ans[N], n, q_time, c_time, block, m, sum;

struct Change {
  int pos, pre, cur;
}change[N];

struct Query {
  int l, r, id, t;
  
  Query(int _l = 0, int _r = 0, int _id = 0, int _t = 0) : l(_l), r(_r), id(_id), t(_t) {}

  bool operator < (const Query & temp) const {
    return  (l / block) != (temp.l / block) ? l < temp.l : (r / block) != (temp.r / block) ? r < temp.r : t < temp.t;
  }
}ask[N];

void add(int x) {
  sum += !num[a[x]]++;
}

void del(int x) {
  sum -= !--num[a[x]];
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m = read();
  for(int i = 1; i <= n; i++)   a[i] = read();
  for(int i = 1; i <= m; i++) {
    char op = getchar();
    while(op != 'Q' && op != 'R') op = getchar();
    if(op == 'Q') {
      q_time++;
      ask[q_time].l = read(), ask[q_time].r = read();
      ask[q_time].id = q_time, ask[q_time].t = c_time;
    }
    else {
      c_time++;
      change[c_time].pos = read(), change[c_time].cur = read();
      change[c_time].pre = a[change[c_time].pos];
      a[change[c_time].pos] = change[c_time].cur;
      //这点一定要注意把a[pos]变成我们要改变的,因为后面可能再次改变这个点,所以他的pre应该是前一次已经改变后的状态。
    }
  }
  for(int i = c_time; i >= 1; i--)  a[change[i].pos] = change[i].pre;//注意从后向前恢复原状。
  block = ceil(exp((log(n) + log(q_time)) / 3));
  int l = 0, r = 0, t = 0;
  sort(ask + 1, ask + 1 + q_time);
  for(int i = 1; i <= q_time; i++) {
    while(r > ask[i].r) del(r--);
    while(r < ask[i].r) add(++r);
    while(l > ask[i].l) add(--l);
    while(l < ask[i].l) del(l++);
    while(t < ask[i].t) {
      t++;
      int pos = change[t].pos;
      if(l <= pos && pos <= r)//同时满足在l , r之间才能进行区间的筛选操作。
        del(pos);
      a[pos] = change[t].cur;//改变其值。
      if(l <= pos && pos <= r)
        add(pos);
    }
    while(t > ask[i].t) {
      int pos = change[t].pos;
      if(l <= pos && pos <= r)
        del(pos);
      a[pos] = change[t].pre;
      if(l <= pos && pos <= r)
        add(pos);
      t--;
    }
    ans[ask[i].id] = sum;
  }
  for(int i = 1; i <= q_time; i++) print(ans[i]), putchar('\n');
  return 0;
}

树上莫队

SP10707 COT2 - Count on a tree II

Thinking

设定两个数组 p r e , s u c pre,suc presuc数组, p r e i pre_i prei数组记录的是节点 i i i第一次进入 d f s dfs dfs的时间戳, s u c i suc_i suci记录的是节点 i i i d f s dfs dfs的时间戳。

如这个例子:

有节点权值及树的形状,满足如下

105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8

在这里插入图片描述

有欧拉序的排列如下。

1 4 8 8 4 3 7 7 6 6 5 5 3 2 2 1

求2, 8的间的,不同的元素。

l c a ( 2 , 8 ) = = 1 ! = 2 , ! = 8 lca(2, 8) == 1 != 2, != 8 lca(2,8)==1!=2,!=8,有 p r e [ 2 ] = 15 > p r e [ 8 ] = 3 pre[2] = 15 > pre[8] = 3 pre[2]=15>pre[8]=3,所以我们取区间 s u c [ 8 ] , p r e [ 2 ] suc[8], pre[2] suc[8],pre[2]也就是

{8 4 3 7 7 6 6 5 5 3 2}​,成对出现的舍弃,最后{8 4 2} + lca(2, 8)刚好是 2 − > 8 2->8 2>8的最短路。所以我们维护的东西就简单了。

再看一种情况,求1, 8之间,显然有 p r e [ 1 ] < p r e [ 8 ] , l c a ( 1 , 8 ) = 1 = m i n i d ( p r e [ x ] , p r e [ y ] ) pre[1] < pre[8], lca(1, 8) = 1 = min_{id}(pre[x], pre[y]) pre[1]<pre[8],lca(1,8)=1=minid(pre[x],pre[y]),所以选定区间 p r e [ 1 ] , p r e [ 8 ] pre[1], pre[8] pre[1],pre[8]即{1, 4, 8}得到了 1 − > 8 1 -> 8 1>8的最短路。所以我们只需要特判 l c a lca lca就行了。

Coding

/*
  Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

const double pi = acos(-1.0);
const double eps = 1e-7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  ll f = 1, x = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    if(c == '-')    f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return f * x;
}

void print(ll x) {
  if(x < 10) {
    putchar(x + 48);
    return ;
  }
  print(x / 10);
  putchar(x % 10 + 48);
}

const int N = 1e5 + 10;

int a[N], b[N], id[N], ans[N], num[N], n, m, block, sum;
int fa[N], son[N], sz[N], dep[N], top[N], pre[N], suc[N], pos[N], tot;
int head[N], to[N], nex[N], cnt = 1;
bool flag[N];

struct Node {
  int l, r, id, lca;
  bool operator < (const Node & t) const {
    return (l / block) != (t.l / block) ? l < t.l : (l / block) & 1 ? r < t.r : r > t.r;
  }
}ask[N];

void add(int x, int y) {
  to[cnt] = y;
  nex[cnt] = head[x];
  head[x] = cnt++;
}

void dfs1(int rt, int f) {
  pre[rt] = ++tot;
  pos[tot] = rt;
  sz[rt] = 1, fa[rt] = f;
  dep[rt] = dep[f] + 1;
  for(int i = head[rt]; i; i = nex[i]) {
    if(to[i] == f)  continue;
    dfs1(to[i], rt);
    if(!son[rt] || sz[to[i]] > sz[son[rt]])
      son[rt] = to[i];
    sz[rt] += sz[to[i]];
  }
  suc[rt] = ++tot;
  pos[tot] = rt;
}

void dfs2(int rt, int tp) {
  top[rt] = tp;
  if(!son[rt])  return ;
  dfs2(son[rt], tp);
  for(int i = head[rt]; i; i = nex[i]) {
    if(to[i] == fa[rt] || to[i] == son[rt]) continue;
    dfs2(to[i], to[i]);
  }
}

int lca(int x, int y) {
  while(top[x] != top[y]) {
    if(dep[top[x]] < dep[top[y]]) swap(x, y);
    x = fa[top[x]];
  }
  return dep[x] < dep[y] ? x : y;
}

void add(int x) {
  sum += (++num[a[x]] == 1);
}

void del(int x) {
  sum -= (--num[a[x]] == 0);
}

void calc(int x) {
  if(!flag[x])  add(x);
  else del(x);
  flag[x] ^= 1;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  n = read(), m =read(), block = sqrt(2 * n);
  for(int i = 1; i <= n; i++) a[i] = b[i] = read();
  sort(b + 1, b + 1 + n);
  int all = unique(b + 1, b + 1 + n) - (b + 1);
  for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + all, a[i]) - b;
  for(int i = 1; i < n; i++) {
    int x = read(), y = read();
    add(x, y);
    add(y, x);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  for(int i = 1; i <= m; i++) {
    int x = read(), y = read();
    if(pre[x] > pre[y]) swap(x, y);
    ask[i].id = i, ask[i].lca = lca(x, y);
    if(ask[i].lca == x) {
      ask[i].l = pre[x];
      ask[i].r = pre[y];
      ask[i].lca = 0;
    }
    else {
      ask[i].l = suc[x];
      ask[i].r = pre[y];
    }
  }
  sort(ask + 1, ask + 1 + m);
  int l = 1, r = 0;
  for(int i = 1; i <= m; i++) {
    while(r > ask[i].r) calc(pos[r--]);
    while(r < ask[i].r) calc(pos[++r]);
    while(l > ask[i].l) calc(pos[--l]);
    while(l < ask[i].l) calc(pos[l++]);
    //lca不在区间里,所以add后需要马上del。
    if(ask[i].lca)      calc(pos[pre[ask[i].lca]]);
    ans[ask[i].id] = sum;
    if(ask[i].lca)      calc(pos[pre[ask[i].lca]]);
  }
  for(int i = 1; i <= m; i++) print(ans[i]), putchar('\n');
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值