题解:P11307 [COTS 2016] 建造费 Pristojba
这道题还挺有启发性的,至少于我来说,让我清晰的知道了 Prim 和 Kruskal 都适用于什么样的问题上。
一些碎碎念
提示:你可以不需要看这一部分,这一部分算是作者解决这道题的基本的思路历程。
可能因为最近写多了数据结构优化建图,所以看到这种区间连边的题就不自主的想到了线段树优化建图,但是显然这不太能行,在无向图上添加的这些多的节点是很显然地会影响原图联通性的。
第二眼想到的就是超级钢琴 trick,用一个五元组表示一堆边当中的最优选择,总的来说就是想办法减少边的状态,接着用线段树求解五元组表示的一堆边的最优选择,然后达成我使用 Kruskal 的目的,看上去复杂度就是 \(O(n\log_2n)\),但这样也是错的,因为这样实质上并未减少需要的状态,很容易会劣化成 \(O(n^2\log n^2)\),你会发现这样不好处理已经在同一集合里的点,最后导致还是把整个区间都加入了优先队列当中。
我在否定了我的很多想法后,只能服输去看官方题解了,但是不好的是官方题解是克罗地亚语,完全看不懂,只能大概知道要用 Prim 和锦标赛树(而且我不是很清楚这是个什么),以及时间复杂度是 \(O(n\log_2^2n)\),我尝试用翻译网站翻译也不是很懂,而且也不清楚用 Prim 的理由,但我大概读出一个意思就是分别用两个数据结构维护已经在树上的点和不在树上的点分别到对方的最短的边,这时候我就意识到只有使用 Prim 才能如此做,因为 Kruskal 所维护的集合是散的,只有到最后才会将所有集合并在一起,而 Prim 却是除初状态和末状态之外,一直处于只有在树上的点的集合和不在树上的点的集合这两个集合的状态。
写了这么久的题在快退役才认识到这点真是抱歉。
Solution
无论使用 Kruskal 还是 Prim 都无法回避边数过多的问题,而 Kruskal 所产生的集合是散的,不太好维护集合与集合间的边,而 Prim 在算法过程中只会产生两个集合,更方便我们维护,所以我们使用 Prim 算法求解。
以下令 \(A\) 表示已经在最小生成树的点中的集合,\(B\) 则为不在树上的点的集合。
我们考虑如何将 \(B\) 中的点一步一步加入 \(A\) 中,因为在一个确定的区间内能覆盖到该区间的点的最小点权加上该区间中的最小点权就是该区间边权最小的边权,而这个信息明显是可以合并的,所以我们可以使用数据结构维护 \(A\) 中点覆盖到 \(B\) 中的区间所产生的贡献,并同时维护 \(B\) 中作为被覆盖的点所产生的贡献,在每个被更新的区间求出最优边权并向上传即可。当然,因为边是双向的,所以也要维护到 \(B\) 到 \(A\) 中的最优边权。
这里使用的是线段树套 set
。
set
维护区间覆盖最小点权,正常线段树维护区间点权。
简述一下过程。
首先一开始 \(A\) 是空的,而 \(B\) 是全集,所以 \(B\) 中覆盖区间是每个点有的区间都要加入,而区间点权每个都是空,\(A\) 相反。
接着每次加入点的时候都在 \(A\) 中插入点所覆盖到的区间到 set
里,并在线段树上删除加入点对应的区间点权,在 \(B\) 中的操作与在 \(A\) 中的操作相反,插入和删除的时候都要更新当前区间的最优边权。
要注意一个区间的最优边权可能会在当前区间直接产生,也可能合并下辖区间的信息。
Code
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ull unsigned long long
#define m_p make_pair
#define m_t make_tuple
#define N 100010
#define inf 0x3f3f3f3f
using namespace std;
using namespace __gnu_pbds;
typedef pair<int, int> pii;
typedef tuple<int, int, int> Edge;
Edge None = {inf, 0, 0};
int n, p[N];
vector<pii> Seq[N];
bitset<N> vis;
struct SegT_Set
{
pii tr[N << 2];
bitset<N << 2> Leaf;
set<pii> s[N << 2];
Edge E[N << 2];
void changE(int k)
{
E[k] = None;
if (!s[k].empty())
{
auto [p1, id1] = *s[k].begin();
auto [p2, id2] = tr[k];
E[k] = {p1 + p2, id1, id2};
}
if (Leaf[k])
return;
E[k] = min({E[k], E[k << 1], E[k << 1 | 1]});
return;
}
void tr_up(int k)
{
tr[k] = min(tr[k << 1], tr[k << 1 | 1]);
return changE(k);
}
void SetNone(int k, int l, int r)
{
if (l == r)
{
Leaf.set(k);
E[k] = None;
tr[k] = {inf, l};
return;
}
int mid = l + r >> 1;
SetNone(k << 1, l, mid);
SetNone(k << 1 | 1, mid + 1, r);
return tr_up(k);
}
void build(int k, int l, int r)
{
if (l == r)
{
Leaf.set(k);
E[k] = None;
tr[k] = {p[l], l};
return;
}
int mid = l + r >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
return tr_up(k);
}
void tr_c(int k, int l, int r, int x, int w)
{
if (l > x || r < x)
return;
if (l == r && r == x)
{
tr[k].first = w;
return changE(k);
}
int mid = l + r >> 1;
if (x <= mid)
tr_c(k << 1, l, mid, x, w);
else
tr_c(k << 1 | 1, mid + 1, r, x, w);
return tr_up(k);
}
void tr_in_del(int k, int l, int r, int x, int y, pii w, bool ty)
{
if (l > y || r < x)
return;
if (l >= x && r <= y)
{
E[k] = None;
if (ty)
s[k].insert(w);
else
s[k].erase(s[k].find(w));
return changE(k);
}
int mid = l + r >> 1;
if (x <= mid)
tr_in_del(k << 1, l, mid, x, y, w, ty);
if (y > mid)
tr_in_del(k << 1 | 1, mid + 1, r, x, y, w, ty);
return tr_up(k);
}
Edge tr_a() { return E[1]; }
} T1, T2;
void Unique()
{
vector<pii> vec;
for (int i = 1; i <= n; i++)
{
sort(Seq[i].begin(), Seq[i].end());
Seq[i].erase(unique(Seq[i].begin(), Seq[i].end()), Seq[i].end());
if (Seq[i].empty())
continue;
vec.clear();
auto [l, r] = Seq[i].front();
for (auto [le, ri] : Seq[i])
{
if (le - 1 > r)
{
vec.push_back({l, r});
l = le;
r = ri;
}
else
r = max(r, ri);
}
vec.push_back({l, r});
Seq[i] = vec;
}
return;
}
void Join(int x)
{
vis.set(x);
T1.tr_c(1, 1, n, x, inf);
T2.tr_c(1, 1, n, x, p[x]);
for (auto [le, ri] : Seq[x])
{
T1.tr_in_del(1, 1, n, le, ri, {p[x], x}, 1);
T2.tr_in_del(1, 1, n, le, ri, {p[x], x}, 0);
}
return;
}
signed main()
{
// auto __IN__ = freopen(".in", "r", stdin);
// auto __OUT__ = freopen(".out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int m, x, l, r;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> p[i];
for (int i = 1; i <= m; i++)
{
cin >> x >> l >> r;
Seq[x].push_back({l, r});
}
Unique();
T1.build(1, 1, n);
T2.SetNone(1, 1, n);
for (int i = 1; i <= n; i++)
for (auto [le, ri] : Seq[i])
T2.tr_in_del(1, 1, n, le, ri, {p[i], i}, 1);
Join(1);
ll MST = 0;
for (int i = 1; i < n; i++)
{
auto [w, x, y] = min(T1.tr_a(), T2.tr_a());
MST += (ll)w;
if (vis[y])
swap(x, y);
Join(y);
}
cout << MST;
return 0;
}