启发式合并
入门
例题
[ABC329F] Colored Ball。
- 题意
给定
如果盒子为空,输出
首先看到对答案有贡献的只有小球的颜色,即种类。因此可以联想到 STL set
实现的自动去重功能。
考虑按题意模拟。若构造一组形如由极小集合合并至极大集合的数据,此算法的最劣复杂度是
此时考虑启发式合并。
我们考虑让集合中元素个数数量小的合并至大的中。此时可以证明时间复杂度是
初看上可能感觉这就是个暴力。但是我们分析一下每个元素被 insert
了多少次。
一个集合中的元素被放入另一个集合中会被 insert
一次。但是这个元素所在的集合的大小至少扩大了一倍。所以一个元素最多被 insert
set
本身带有的
在这里,对于两个大小不一样的集合,我们将小的集合合并到大的集合中,而不是将大的集合合并到小的集合中。
为什么呢?这个集合的大小可以认为是集合的高度(在正常情况下),而我们将集合高度小的并到高度大的显然有助于我们找到父亲。
让高度小的树成为高度较大的树的子树,这个优化可以做到单次
while(q--) {
cin >> x >> y;
if(s[x].size() < s[y].size()) {
for(auto i : s[x])
s[y].insert(i);
s[x].clear();
cout << s[y].size() << '\n';
}
else {
for(auto i : s[y])
s[x].insert(i);
s[y].clear(), swap(s[x], s[y]);
cout << s[y].size() << '\n';
}
}
P3201 [HNOI2009] 梦幻布丁
dsu on tree
使用场景:需统计子树内节点的信息。
算法原理:对于一个一
时间复杂度分析:瓶颈在于清空,显然任意一颗轻子树大小小于二分之一原子树,即最劣时间复杂度
例题
[ABC350G] Mediator
算是 ABC329F 的延申。
首先一个点是否与询问点对相邻,可以转换为对父亲的讨论。
下列情况是有解的:
且 不是根节点,答案为 ,答案为 ,答案为
画图分析较为易懂。
于是我们只需要维护父亲即可,考虑启发式合并。
每次将连通块大小较小的合并至较大的连通块内,对于每次这种操作暴力 dfs 修改
连通块大小可以用并查集轻松维护。
证明复杂度:每次连通块大小最多扩大一倍,所以复杂度
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
const int mod = 998244353;
int n, Q, u, v, op, ans, fa[N];
vector<int> g[N];
inline void dfs(int x, int last) {
fa[x] = last;
for(auto u : g[x])
if(u != last) dfs(u, x);
return ;
}
namespace USF {
int Fa[N], sz[N];
inline void init() {
for(int i = 1 ; i < N ; ++ i)
Fa[i] = i, sz[i] = 1;
return ;
}
inline int find(int x) {
if(x != Fa[x]) Fa[x] = find(Fa[x]);
return Fa[x];
}
inline void merge(int x, int y) {
int fx = find(x), fy = find(y);
if(fx == fy) return ;
if(sz[fx] < sz[fy]) swap(x, y), swap(fx, fy);
dfs(y, x);
sz[fx] += sz[fy], Fa[fy] = fx;
g[x].pb(y), g[y].pb(x);
return ;
}
}
using namespace USF;
inline int query(int u, int v) {
if(fa[u] == fa[v] && fa[u]) return fa[u];
if(fa[fa[u]] == v) return fa[u];
if(fa[fa[v]] == u) return fa[v];
return 0;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
init();
while(Q --) {
cin >> op >> u >> v;
op = 1 + ((op * (1 + ans)) % mod) % 2;
u = 1 + ((u * (1 + ans)) % mod) % n;
v = 1 + ((v * (1 + ans)) % mod) % n;
if(op == 1) merge(u, v);
else {
ans = query(u, v);
cout << ans << '\n';
}
}
return 0;
}
CF375D Tree and Queries
算是很典的题了。
分析:
1.离线,将询问挂到每个点上
2.轻重链剖分,对于点
3.每个除了叶子的节点都有重儿子,从根节点开始一直走重儿子形成的链称之为重链
4.对于任意点
5.对于点
6.若点
不难发现 dsu on tree 有一个固定的流程。即:轻儿子
于是这类 dsu 的题目其实是有一个较为固定的模板的。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int n, u, v, Q, heavy, c[N], sz[N], son[N], ans[N], cnt[N], num[N];
struct Node {
int id, k;
Node(int iid = 0, int kk = 0) {
id = iid, k = kk;
}
};
vector<int> g[N];
vector<Node> q[N];
inline void dfs1(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
dfs1(u, x);
sz[x] += sz[u];
if(sz[son[x]] < sz[u]) son[x] = u;
}
return ;
}
inline void add(int x, int last, int val) {
if(val == -1) -- num[cnt[c[x]]];
cnt[c[x]] += val;
if(val == 1) ++ num[cnt[c[x]]];
for(auto u : g[x])
if(u != last && u != heavy) add(u, x, val);
return ;
}
inline void dfs2(int x, int last, bool flag) {
for(auto u : g[x])
if(u != last && u != son[x]) dfs2(u, x, 0);
if(son[x]) dfs2(son[x], x, 1), heavy = son[x];
add(x, last, 1);
for(auto i : q[x])
ans[i.id] = num[i.k];
heavy = 0;
if(! flag) add(x, last, -1);
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
for(int i = 1 ; i <= n ; ++ i)
cin >> c[i];
for(int i = 1 ; i < n ; ++ i) {
cin >> u >> v;
g[u].pb(v), g[v].pb(u);
}
for(int i = 1 ; i <= Q ; ++ i) {
cin >> u >> v;
q[u].pb(Node(i, v));
}
dfs1(1, -1);
dfs2(1, -1, 0);
for(int i = 1 ; i <= Q ; ++ i)
cout << ans[i] << '\n';
return 0;
}
其中 dfs1
dfs2
add
可以直接固定住形成一个较为稳定的板子。
CF570D Tree Requests
改一下 add
即可,没啥变化。
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 5e5 + 5;
int n, u, v, Q, heavy, sz[N], son[N], dep[N], cnt[N][27];
bool ans[N];
char c[N];
struct Node {
int id, k;
Node(int iid = 0, int kk = 0) {
id = iid, k = kk;
}
};
vector<int> g[N];
vector<Node> q[N];
inline void dfs1(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
dep[u] = dep[x] + 1;
dfs1(u, x);
sz[x] += sz[u];
if(sz[son[x]] < sz[u]) son[x] = u;
}
return ;
}
inline void add(int x, int last, int val) {
cnt[dep[x]][c[x] - 'a' + 1] += val;
for(auto u : g[x])
if(u != last && u != heavy) add(u, x, val);
return ;
}
inline void dfs2(int x, int last, bool flag) {
for(auto u : g[x])
if(u != last && u != son[x]) dfs2(u, x, 0);
if(son[x]) dfs2(son[x], x, 1), heavy = son[x];
add(x, last, 1);
for(auto i : q[x]) {
int sum = 0;
for(int j = 1 ; j <= 26 ; ++ j)
sum += (cnt[i.k][j] & 1);
ans[i.id] = (sum <= 1);
}
heavy = 0;
if(! flag) add(x, last, -1);
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
for(int i = 2 ; i <= n ; ++ i) {
cin >> u;
g[u].pb(i), g[i].pb(u);
}
for(int i = 1 ; i <= n ; ++ i)
cin >> c[i];
for(int i = 1 ; i <= Q ; ++ i) {
cin >> u >> v;
q[u].pb(Node(i, v));
}
dep[1] = 1;
dfs1(1, -1);
dfs2(1, -1, 0);
for(int i = 1 ; i <= Q ; ++ i)
if(ans[i]) cout << "Yes\n";
else cout << "No\n";
return 0;
}
CF246E Blood Cousins Return
在每个深度上维护 std :: set
即可。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 5e5 + 5;
int n, u, v, Q, heavy, sz[N], son[N], dep[N], cnt[N], ans[N];
struct Node {
int id, k;
Node(int iid = 0, int kk = 0) {
id = iid, k = kk;
}
};
string s[N];
vector<int> g[N];
vector<Node> q[N];
set<string> S[N];
inline void dfs1(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
dep[u] = dep[x] + 1;
dfs1(u, x);
sz[x] += sz[u];
if(sz[son[x]] < sz[u]) son[x] = u;
}
return ;
}
inline void add(int x, int last, int val) {
if(val == 1) S[dep[x]].insert(s[x]);
else S[dep[x]].clear();
for(auto u : g[x])
if(u != last && u != heavy) add(u, x, val);
return ;
}
inline void dfs2(int x, int last, bool flag) {
for(auto u : g[x])
if(u != last && u != son[x]) dfs2(u, x, 0);
if(son[x]) dfs2(son[x], x, 1), heavy = son[x];
add(x, last, 1);
for(auto i : q[x])
if(i.k + dep[x] <= n) ans[i.id] = S[i.k + dep[x]].size();
heavy = 0;
if(! flag) add(x, last, -1);
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for(int i = 1 ; i <= n ; ++ i) {
cin >> s[i] >> u;
g[u].pb(i), g[i].pb(u);
}
cin >> Q;
for(int i = 1 ; i <= Q ; ++ i) {
cin >> u >> v;
q[u].pb(Node(i, v));
}
dfs1(0, -1);
dfs2(0, -1, 0);
for(int i = 1 ; i <= Q ; ++ i)
cout << ans[i] << '\n';
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!