主席树

引入

我们考虑对于一个长度为 \(n\) 的序列去找第 \(k\) 小,如果不用排序的话(虽然用了桶),可以利用一个桶匠所有数纪录下来,然后在桶上做二分即可(不会这个都不会吧),那么对于一个区间的话,我们便可以在区间 \([l, r]\) 上开桶然后做二分,不过这个桶我们该如何维护呢,首先我们想到前缀和,因为用前缀和可以快速求区间,可是如果直接用数组的话,我们无法直接快速的算出区间 \([l, r]\) 的桶,于是我们想到了平衡树的操作,将 \(r\) 处的平衡树把 \(l - 1\) 处的平衡树的一部分去点即可,不过想法很好,空间会炸,而且这很明显没用完美利用平衡树,所以考虑用一种可以将 \(n^2\) 空间换成一个小的空间的线段树。

思路

考虑到在这 \(n\) 次加点中,每次加点最多加一个点,而对于一颗线段树,只有一条链上的值发生了改变,从而我们将改变的数以及所在结构抽出来,形成一颗新的线段树,我们便用 \(log\) 的空间建了一个新的线段树,不过由于是在之前的基础上加的,正真在做这颗线段树的时候,是要算上前面的,所以我们需要将这棵树连回去,所以我们将这棵树的结构在原来位置上存在的边加回来,便是连上了,从而我们就成功的将 \(n ^ 2\) 空间缩小到了 \(nlogn\),那么在查询的时候,我们只需要考虑左边有多少个比它小的,所以只需要将前缀和的方式处理掉 \(l - 1\) 及以前有多少个数即可。

code

#include <iostream>

using namespace std;

const int MaxN = 2e5 + 10, inf = 1e9;

struct Node {
  int sum, l, r;
} a[MaxN << 5];

int rt[MaxN], n, m, tot;

void insert(int &x, int y, int l, int r, int v) {
  x = ++tot, a[x].sum = a[y].sum + 1;
  if (l == r) {
    return;
  }
  int mid = l + r >> 1;
  if (v <= mid) insert(a[x].l, a[y].l, l, mid, v), a[x].r = a[y].r;
  else insert(a[x].r, a[y].r, mid + 1, r, v), a[x].l = a[y].l;
}

int query(int x, int y, int l, int r, int k) {
  if (l == r) {
    return l;
  }
  int v = a[a[x].l].sum - a[a[y].l].sum, mid = l + r >> 1;
  if (k <= v) return query(a[x].l, a[y].l, l, mid, k);
  else return query(a[x].r, a[y].r, mid + 1, r, k - v);
}

int main() {
  cin >> n >> m;
  for (int i = 1, x; i <= n; i++) {
    cin >> x;
    insert(rt[i], rt[i - 1], 0, inf, x);
  }
  for (int i = 1, l, r, k; i <= m; i++) {
    cin >> l >> r >> k;
    cout << query(rt[r], rt[l - 1], 0, inf, k) << endl;
  }
  return 0;
}

P2839 [国家集训队] middle

当然,这个东西不一定是一个权值线段树

思路

由于要算中位数,所以我们先思考给了 \([l, r]\) 以及中位数 \(k\) 怎么保证这个是中位数,那么很显然将所有 \(< k\) 的改为 \(-1\)\(\ge k\) 的改为 \(1\),然后统计一下区间和是否 \(\ge 0\) 如果 \(\ge 0\) 就是可以不过可能可以再大些,否则就是大了,不成立,于是我们考虑二分去找中位数,那么对于会变动的区间又该如何呢?由于不考虑方案,所以我们只需要找方案里答案最大的即可,由于中间的 \([b + 1, c - 1]\) 必选,所以将整个变动区间分为三部分,中间直接求,两边取最大值即可,那我们不可能去暴力改变数组,所以可以用主席树维护当每一个点作为中位数时的每一个数的值。

code

#include <iostream>
#include <algorithm>

using namespace std;

const int MaxN = 40010, inf = 1e9;

struct Node {
  int x, l, r, lmax, rmax;
} a[MaxN << 5];

struct S {
  int x, y;

  bool operator<(const S &j) const {
    return x < j.x;
  }
} b[MaxN];

int rt[MaxN], n, m, tot;

void insert(int &x, int y, int k, int v, int l = 1, int r = n) {
  x = ++tot, a[x] = a[y];
  if (l == r) {
    a[x].x = v, a[x].lmax = a[x].rmax = v;
    return;
  }
  int mid = l + r >> 1;
  if (k <= mid)
    insert(a[x].l, a[y].l, k, v, l, mid);
  else
    insert(a[x].r, a[y].r, k, v, mid + 1, r);
  a[x].lmax = max(a[a[x].l].lmax, a[a[x].r].lmax + a[a[x].l].x);
  a[x].rmax = max(a[a[x].r].rmax, a[a[x].l].rmax + a[a[x].r].x);
  a[x].x = a[a[x].l].x + a[a[x].r].x;
}

int query(int x, int pl, int pr, int l = 1, int r = n) {
  if (pl <= l && r <= pr) {
    return a[x].x;
  }
  if (l > pr || r < pl) return 0;
  int mid = l + r >> 1;
  return query(a[x].l, pl, pr, l, mid) + query(a[x].r, pl, pr, mid + 1, r);
}

int queryl(int x, int pl, int pr, int l = 1, int r = n) {
  if (pl <= l && r <= pr) {
    return a[x].lmax;
  }
  int mid = l + r >> 1;
  if (mid >= pr) {
    return queryl(a[x].l, pl, pr, l, mid);
  } else if (mid < pl) {
    return queryl(a[x].r, pl, pr, mid + 1, r);
  }
  return max(queryl(a[x].l, pl, pr, l, mid), query(a[x].l, pl, pr, l, mid) + queryl(a[x].r, pl, pr, mid + 1, r));
}

int queryr(int x, int pl, int pr, int l = 1, int r = n) {
  if (pl <= l && r <= pr) {
    return a[x].rmax;
  }
  int mid = l + r >> 1;
  if (mid >= pr) {
    return queryr(a[x].l, pl, pr, l, mid);
  } else if (mid < pl) {
    return queryr(a[x].r, pl, pr, mid + 1, r);
  }
  return max(queryr(a[x].r, pl, pr, mid + 1, r), query(a[x].r, pl, pr, mid + 1, r) + queryr(a[x].l, pl, pr, l, mid));
}

bool Check(int x, int a, int b, int c, int d) {
  int cnt = 0;
  if (b + 1 <= c - 1) {
    cnt += query(rt[x], b + 1, c - 1);
  }
  cnt += queryr(rt[x], a, b) + queryl(rt[x], c, d);
  return cnt >= 0;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> b[i].x, b[i].y = i;
  }
  sort(b + 1, b + n + 1);
  for (int i = 1; i <= n; i++) {
    insert(rt[i], rt[i - 1], b[i].y, 1);
  }
  rt[n + 1] = rt[n];
  for (int i = 2; i <= n; i++) {
    insert(rt[i + n], rt[i + n - 1], b[i - 1].y, -1);
  }
  cin >> m;
  for (int tmp[5] = {0}, x = 0; m; m--) {
    cin >> tmp[0] >> tmp[1] >> tmp[2] >> tmp[3];
    tmp[0] = (tmp[0] + x) % n + 1, tmp[1] = (tmp[1] + x) % n + 1, tmp[2] = (tmp[2] + x) % n + 1, tmp[3] = (tmp[3] + x) % n + 1;
    sort(tmp, tmp + 4);
    int l = 1, r = n;
    while (l < r) {
      int mid = l + r + 1 >> 1;
      Check(mid + n, tmp[0], tmp[1], tmp[2], tmp[3]) ? l = mid : r = mid - 1;
    }
    cout << b[l].x << endl;
    x = b[l].x;
  }
  return 0;
}

如果与树相结合

Subtree Minimum Query

思路

哇,一眼剖分,两眼我不想做了。

由于是距离,所以我们考虑将深度做为历史版本,不过由于是在子树内,所以用 dfs序 来约束即可

code

#include <iostream>
#include <map>
#include <algorithm>
#include <vector>

using namespace std;
using ll = long long;

const int MaxN = 100010;

struct Node {
  ll x, l, r;

  Node() : x(1e9), l(0), r(0) {}
} a[MaxN << 5];

ll rt[MaxN], dfn[MaxN], dep[MaxN], mxdfn[MaxN], lt[MaxN], c[MaxN], n, m, tot, cnt, maxdep, q;
vector<int> g[MaxN], e[MaxN];

void insert(ll &x, ll y, ll p, ll w, int l = 1, int r = n) {
  a[++tot] = a[y], x = tot, a[x].x = min(a[x].x, w);
  if (l == r) return;
  int mid = l + r >> 1;
  if (p <= mid) insert(a[x].l, a[y].l, p, w, l, mid);
  else insert(a[x].r, a[y].r, p, w, mid + 1, r);
}

ll query(int x, int pl, int pr, int l = 1, int r = n) {
  if (pl <= l && r <= pr) return a[x].x;
  if (l > pr || r < pl) return 1e9;
  int mid = l + r >> 1;
	return min(query(a[x].l, pl, pr, l, mid), query(a[x].r, pl, pr, mid + 1, r));
}

void DFS(int x, int fa) {
  dfn[x] = ++cnt, maxdep = max(maxdep, (dep[x] = dep[fa] + 1)), e[dep[x]].push_back(x);
  for (int i : g[x]) if (i != fa) DFS(i, x);
  mxdfn[x] = cnt;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> c[i];
  }
  for (int i = 1, u, v; i < n; i++) {
    cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  DFS(m, 0);
  for (int i = 1; i <= n; i++) {
    for (int j : e[i]) {
      lt[i] ? insert(rt[dfn[j]], rt[lt[i]], dfn[j], c[j]) : insert(rt[dfn[j]], rt[lt[i - 1]], dfn[j], c[j]);
      lt[i] = dfn[j];
    }
  }
  cin >> q;
  for (ll u, v, lst = 0; q; q--) {
    cin >> u >> v, u += lst, v += lst, u %= n, v %= n, u++;
    cout << (lst = query(rt[lt[min(dep[u] + v, maxdep)]], dfn[u], mxdfn[u])) << '\n';
  }
  return 0;
}

P4197 Peaks

哇,上一题是树,这一题咋成图了呢?

二话不说开始 kruskal 重构树,那么什么是 kruskal 重构树呢?

首先,kruskal 是将边排序后进行生成树,会保留原来的边,而 kruskal 重构树则是将边权拆成一个点,将边权的值附在点上,那么它将满足以下性质:

  1. 点权一定关于深度有单调性
posted @ 2024-05-30 14:34  yabnto  阅读(11)  评论(0编辑  收藏  举报
  1. 1 イエスタデイ(翻自 Official髭男dism) 茶泡饭,春茶,kobasolo
  2. 2 光辉岁月 Audio artist
  3. 3 名前を呼ぶよ Audio artist
  4. 4 战歌 Audio artist
  5. 5 時を越えた想い Audio artist
  6. 6 所念皆星河 Audio artist
  7. 7 See you again Audio artist
イエスタデイ(翻自 Official髭男dism) - 茶泡饭,春茶,kobasolo
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 藤原聡

作曲 : 藤原聡

何度失ったって

取り返して見せるよ

雨上がり 虹がかかった空みたいな

君の笑みを

例えばその代償に

誰かの表情を

曇らせてしまったっていい

悪者は僕だけでいい

本当はいつでも

誰もと思いやりあっていたい

でもそんな悠長な理想論は

ここで捨てなくちゃな

遥か先で 君へ 狙いを定めた恐怖を

遥か先で 君へ 狙いを定めた恐怖を

どれだけ僕は

はらい切れるんだろう?

半信半疑で 世間体

半信半疑で 世間体

気にしてばっかのイエスタデイ

ポケットの中で怯えたこの手は

まだ忘れられないまま

「何度傷ついたって

「何度傷ついたって

仕方ないよ」と言って

うつむいて君が溢した

儚くなまぬるい涙

ただの一粒だって

僕を不甲斐なさで 溺れさせて

理性を奪うには十分過ぎた

街のクラクションもサイレンも

街のクラクションもサイレンも

届きやしないほど

遥か先へ進め 身勝手すぎる恋だと

遥か先へ進め 身勝手すぎる恋だと

世界が後ろから指差しても

振り向かず進め必死で

振り向かず進め必死で

君の元へ急ぐよ

道の途中で聞こえたSOS さえ

気づかないふりで

バイバイイエスタデイ ごめんね

バイバイイエスタデイ ごめんね

名残惜しいけど行くよ

いつかの憧れと違う僕でも

ただ1人だけ 君だけ

守るための強さを

何よりも望んでいた この手に今

遥か先へ進め

遥か先へ進め

幼すぎる恋だと

世界が後ろから指差しても

迷わずに進め 進め

2人だけの宇宙へと

ポケットの中で震えたこの手で今

君を連れ出して

未来の僕は知らない

だから視線は止まらない

謎めいた表現技法

意味深な君の気性

アイラブユーさえ

アイラブユーさえ

風に 飛ばされそうな時でも

不器用ながら繋いだ この手はもう

決して離さずに

虹の先へ