今天vp遇到两道启发式合并题,觉得很有意思然后记录一下qwq
最近感觉和队友在一起a题的时候很开心O.o,但是这种日子也不长了
F. Strange Memory
题目链接:Problem - F - Codeforces
题目描述
给出一颗带点权、根节点为1的树,求出 $\sum_{i=1}^{n}\sum_{j=i+1}^{n}[a_i \oplus a_j = a_{lca(i,j)}](i \oplus j)$。
$ \oplus$ 代表异或,也就是求出两节点权值满足 $a_i \oplus a_j = a_{lca(i,j)}$ 时, $i \oplus j$ 的和。
思路
首先看到题目第一眼想到的就是 dsu on tree
,扫描每颗子树的根节点 $u$,然后枚举子树内的节点 $v$ ,保存 $a_u \oplus a_v$ 然后再枚举子树内的节点,然后计数。
后来发现贡献其实是由根节点的所有儿子所属的子树相互产生的。所以我们必须枚举儿子的子树,想到根节点是不断变化的,保存 $a_u \oplus a_v$ 有些许困难,所以换一种思路,我们保存 $a_v$, 然后枚举时再由 $a_v \oplus a_u$ 来计数。
这道题前前后后花了快两个小时,之前学的都忘干净了555,不过好在最后ac了…
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include<bits/stdc++.h>
using namespace std; const int N = 1e5 + 5, M = 1e6 + 5;
int n, a[N]; vector< int > adj[N]; int siz[N], son[N], dfn[N], idx[N]; int num[M][18][2]; void dfs1(int x, int p) { siz[x] = 1; dfn[x] = ++ dfn[0]; idx[dfn[0]] = x; for (auto &y : adj[x]) { if (y ^ p) { dfs1(y, x); siz[x] += siz[y]; if (siz[y] > siz[son[x]]) { son[x] = y; } } } } long long ans = 0; void dfs2(int x, int p, bool keep) { for (auto &y : adj[x]) { if (y != son[x] && y != p) { dfs2(y, x, false); } } if (son[x]) { dfs2(son[x], x, true); } for (auto &y:adj[x]) { if (y != son[x] && y != p) { for (int i = dfn[y]; i <= dfn[y] + siz[y] - 1; ++i) { int v = idx[i]; if ((a[v] ^ a[x]) <= 1000000) { for (int j = 0; j < 18; ++j) { ans += (1LL << j) * num[a[v] ^ a[x]][j][!(v >> j & 1)]; } } } for (int i = dfn[y]; i <= dfn[y] + siz[y] - 1; ++i) { int v = idx[i]; for (int j = 0; j < 18; ++j) { ++ num[a[v]][j][v >> j & 1]; } } } } for (int j = 0; j < 18; ++j) { ++ num[a[x]][j][x >> j & 1]; } if (!keep) { for (int i = dfn[x]; i <= dfn[x] + siz[x] - 1; ++i) { int v = idx[i]; for (int j = 0; j < 18; ++j) { --num[a[v]][j][v >> j & 1]; } } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++ i) { scanf("%d", &a[i]); } for (int i = 1; i < n; ++ i) { int u, v; scanf("%d %d", &u, &v); adj[u].push_back(v); adj[v].push_back(u); } dfs1(1, 0); dfs2(1, 0, true); printf("%lld\n", ans); return 0; }
|
K. Ragdoll
题目链接:Problem - K - Codeforces
题目描述
刚开始有 $n$ 个空集合,有 $m$ 次操作,每次操作如下:
- 新建一个集合,向其中插入一个权值为 $v$ 的节点 $x$
- 合并 $x$ 与 $y$ 所属的集合,若已在同一集合则不操作
- 修改节点 $x$ 的权值为 $v$
每次操作后,都需要输出所有集合内 $\gcd \left(a_i,a_j\right)=a_i \oplus a_j$ 的个数和
思路
这题思维难度相对前一题较简单,我们只需要动态地维护所有集合内的数的个数即可。
思考 $\gcd \left(a_i,a_j\right)=a_i \oplus a_j$ 这个公式,通过打表观察到每个 $a_i$ 满足条件的 $a_j$ 个数并不多,最多只有72
个,所以我们直接存储每个数满足条件的数。
每次合并时我们暴力遍历两个集合中 $size$ 相对小的集合,然后计数。
修改操作很简单,就不讲解了。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include<bits/stdc++.h> using namespace std;
const int N = 3e5 + 5, M = 2e5 + 5;
vector< int > g[M]; vector< int > gg[M]; void init(){ for(int i = 1;i< M; ++ i){ for(int j = i;j < M; j += i){ g[j].push_back(i); } } for(int i = 1;i < M; ++ i){ for(auto x : g[i]){ int b = x ^ i; if (b < M) { if(gcd(b,i) == x){ gg[i].push_back(b); } } } } } int f[N], n, m; int a[N]; long long ans; unordered_map< int, int > tree[N]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void merge(int x, int y) { x = find(x); y = find(y); if (x == y) { return; } if (tree[x].size() > tree[y].size()) { swap(x, y); } for (auto &[u, cnt] : tree[x]) { for (auto &v:gg[u]){ if(tree[y].find(v)!=tree[y].end()) ans += tree[y][v] * cnt; } } for (auto &[u, cnt] : tree[x]) { tree[y][u] += cnt; } f[x] = y; tree[x].clear(); } void change(int x, int v) { int y = find(x); for (auto &v : gg[a[x]]) { ans -= tree[y][v]; } tree[y][a[x]] --; ++ tree[y][v]; a[x] = v; for (auto &v : gg[a[x]]) { ans += tree[y][v]; } } int main() {
init(); scanf("%d %d", &n, &m); for (int i = 1; i <= n; ++ i) { scanf("%d",&a[i]); ++ tree[i][a[i]]; } iota(f + 1, f + n + m + 1, 1); while (m -- ) { int x, y, op; scanf("%d %d %d", &op, &x, &y); if (op == 1) { a[x] = y; ++ tree[x][y]; } else if (op == 2) { merge(x, y); } else { change(x, y); } printf("%lld\n",ans); } return 0; }
|