2026-01-19~20 hetao1733837 的刷题笔记
01-19
LGP4768 [NOI2018] 归程
原题链接:[NOI2018] 归程
分析
感觉 INF 天前就要写这题了,一直没写......
发现自己没看完题面
好像是利用了 Kruskal \operatorname{Kruskal} Kruskal 重构树的一些神奇小性质......我已经十五分钟愣神了......似乎先考虑离线是容易的。
那么,我似乎可以一点一点往里面加一些边......似乎是对的......
然后上升到在线......
我们使用 Dijkstra \operatorname{Dijkstra} Dijkstra 预处理所有点到起点的最短距离,问题转化为:只保留海拔大于 p p p 的边,问 v v v 所在连通分量的点权的最小值。与连通分量有关......然后就想到 Kruskal \operatorname{Kruskal} Kruskal 重构树了?
怎么说呢?从 HT 那个 PPT 去理解, MST \operatorname{MST} MST 似乎进行了一些妥协,使得边权尽可能小了......
那么,我们建出来 Kruskal 重构树之后,问题转化为子树最小值?哦?
那么,一个 dfs 再加个倍增完美解决。
我似乎不太会啊......
所以,这是个拼板子?
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 400005, M = 800005;
int T;
int n, m, q, k, s;
struct node1{
int u, v, l, a;
}e[M], p[M];
bool cmp(node1 a, node1 b){
return a.a > b.a;
}
struct node2{
int v, nxt;
}tr[M];
struct node3{
int v, nxt, w;
}t[M];
struct node4{
int u, w;
};
bool operator<(node4 a, node4 b){
return a.w > b.w;
}
int tot1, tot2;
int head1[N], head2[N];
void add1(int u, int v){
tr[++tot1].v = v;
tr[tot1].nxt = head1[u];
head1[u] = tot1;
}
void add2(int u, int v, int w){
t[++tot2].v = v;
t[tot2].nxt = head2[u];
t[tot2].w = w;
head2[u] = tot2;
}
int d[N];
bool vis[N];
void dijkstra(int S){
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
priority_queue<node4> q;
d[S] = 0;
q.push({S, d[S]});
while (!q.empty()){
auto tmp = q.top();
q.pop();
if (vis[tmp.u])
continue;
for (int i = head2[tmp.u]; i; i = t[i].nxt){
int v = t[i].v;
if (vis[v])
continue;
if (d[v] > d[tmp.u] + t[i].w){
d[v] = d[tmp.u] + t[i].w;
q.push({v, d[v]});
}
}
}
for (int i = 1; i <= n; i++){
p[i].l = d[i];
}
}
int fa[N];
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int de[N], f[N][21];
void dfs(int u, int pa){
de[u] = de[pa] + 1;
f[u][0] = pa;
for (int i = 1; i <= 19; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (int i = head1[u]; i; i = tr[i].nxt){
int v = tr[i].v;
dfs(v, u);
p[u].l = min(p[u].l, p[v].l);
}
}
int getmn(int x, int y){
for (int i = 19; i >= 0; i--){
if (de[x] - (1 << i) >0 && p[f[x][i]].a > y)
x = f[x][i];
}
return p[x].l;
}
int ans = 0;
void Kruskal(){
int cnt1 = 0, cnt2 = n;
for (int i = 1; i <= (n << 1); i++){
fa[i] = i;
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i++){
int u = e[i].u, v = e[i].v;
int fu = find(u), fv = find(v);
if (fu != fv){
add1(++cnt2, fu);
add1(cnt2, fv);
fa[fu] = cnt2;
fa[fv] = cnt2;
p[cnt2].a = e[i].a;
cnt1++;
}
if (cnt1 == n - 1){
break;
}
}
dfs(cnt2, 0);
while (q--){
int v0, p0;
cin >> v0 >> p0;
int x = (k * ans + v0 - 1) % n + 1;
int y = (k * ans + p0) % (s + 1);
ans = getmn(x, y);
cout << ans << '\n';
}
}
void init(){
memset(e, 0, sizeof(e));
memset(head1, 0, sizeof(head1));
memset(head2, 0, sizeof(head2));
memset(f, 0, sizeof(f));
tot1 = 0;
tot2 = 0;
ans = 0;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while (T--){
init();
cin >> n >> m;
for (int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v >> e[i].l >> e[i].a;
add2(e[i].u, e[i].v, e[i].l);
add2(e[i].v, e[i].u, e[i].l);
}
for (int i = n + 1; i <= (n << 1); i++){
p[i].l = 0x3f3f3f3f;
}
dijkstra(1);
cin >> q >> k >> s;
Kruskal();
}
}
01-20
CF891C Envy
原题链接1:CF891C Envy
原题链接2:C. Envy
分析
啊?咋是最小生成树的不唯一性?这咋做?
考虑暴力,每次强制先选这几条边,那么,复杂度 O ( q n l o g 2 n ) O(qnlog_2n) O(qnlog2n)。
考虑离线?先求出剩余连通块的 M S T MST MST,不断加边?那似乎也不是很优吧......
难道是 Kruskal \operatorname{Kruskal} Kruskal 重构树上的距离?那能评......蓝......似乎是比较对的。
假了......
我们知道,在最小生成树中边权小于某个阈值 w w w 的边形成的连通分量是确定的。
那么,问题转化为:对于若干边权同为 w w w 的边,是否能同时成功合成对应连通分量。使用可撤销并查集即可。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m, q, cur;
int u[N], v[N], w[N], fa[N], sz[N], inp[N], ans[N];
vector<int> ask[N];
int top;
struct node1{
int u, v, w;
}e[N];
struct node2{
int fu, fv;
}un[N];
bool cmp1(node1 x, node1 y){
return x.w < y.w;
}
bool cmp2(int x, int y){
return w[x] < w[y];
}
bool cmp3(const vector<int> &x, const vector<int> &y){
return w[x[1]] < w[y[1]];
}
int find(int x){
while (x != fa[x]) x = fa[x];
return x;
}
void merge(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy)
return;
if (sz[fx] > sz[fy])
swap(fx, fy);
un[++top] = {fx, fy};
sz[fy] += sz[fx];
fa[fx] = fy;
}
void rollback(int target){
while (top > target){
int fx = un[top].fu, fy = un[top].fv;
fa[fx] = fx;
sz[fy] -= sz[fx];
top--;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++){
fa[i] = i;
sz[i] = 1;
}
for (int i = 1; i <= m; i++){
cin >> u[i] >> v[i] >> w[i];
e[i] = {u[i], v[i], w[i]};
}
sort(e + 1, e + m + 1, cmp1);
cin >> q;
int tot = 0;
for (int cs = 1; cs <= q; cs++){
int k;
cin >> k;
ans[cs] = 1;
for (int i = 1; i <= k; i++){
cin >> inp[i];
}
sort(inp + 1, inp + k + 1, cmp2);
for (int i = 1; i <= k; i++){
if (i == 1 || w[inp[i]] != w[inp[i-1]]){
ask[++tot].clear();
ask[tot].push_back(cs);
}
ask[tot].push_back(inp[i]);
}
}
cur = 1;
sort(ask + 1, ask + tot + 1, cmp3);
for (int i = 1; i <= tot; i++){
bool flag = true;
while (cur <= m && e[cur].w < w[ask[i][1]]){
merge(e[cur].u, e[cur].v);
cur++;
}
int old_top = top;
int sz_vec = ask[i].size();
for (int j = 1; j < sz_vec; j++){
int id = ask[i][j];
if (find(u[id]) == find(v[id])){
flag = false;
}
merge(u[id], v[id]);
}
rollback(old_top);
ans[ask[i][0]] &= flag;
}
while (cur <= m){
merge(e[cur].u, e[cur].v);
cur++;
}
for (int i = 1; i <= q; i++){
cout << (ans[i] ? "YES" : "NO") << '\n';
}
}
难道被卡常了?不太清楚。
LGP5631 最小mex生成树
原题链接:最小mex生成树
分析
似乎,我枚举 w w w 这一维,钦定生成树的 mex \operatorname{mex} mex 似乎是可行的。那怎么判有解?用并查集暴力?时间爆炸!
那怎么办?看一眼 Hint ......
不用并查集,我们把图中所有边权为 w w w 的边都删了,图依然联通不就行了!
然后,为啥这里可以想到分治?因为这里相当于删掉了图的一部分......对权值区间分治,维护区间外的可撤销并查集即可。
啊?
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005, M = 2000005;
int n, m;
struct node {
int u, v, w;
} e[M];
bool cmp(node x, node y){
return x.w < y.w;
}
int fa[N], sz[N];
int find(int x){
return x == fa[x] ? x : find(fa[x]);
}
int merge(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy){
return 0;
}
if (sz[fx] > sz[fy]){
fa[fy] = fx;
sz[fx] += sz[fy];
return fy;
}
else{
fa[fx] = fy;
sz[fy] += sz[fx];
return fx;
}
}
void del(int x) {
sz[fa[x]] -= sz[x];
fa[x] = x;
}
void work(int l, int r, int pos){
if (l == r) {
if (sz[find(1)] == n){
cout << l;
exit(0);
}
return ;
}
int mid = (l + r) >> 1;
int tmp = pos;
int lst = 0;
vector<int> k;
while (e[pos].w <= r && pos <= m){
if (e[pos].w > mid){
lst = merge(e[pos].u, e[pos].v);
if (lst) k.push_back(lst);
}
pos++;
}
work(l, mid, tmp);
for (int i = k.size() - 1; i >= 0; i--){
del(k[i]);
}
k.clear();
pos = tmp;
while (e[pos].w <= mid && pos <= m){
lst = merge(e[pos].u, e[pos].v);
if (lst)
k.push_back(lst);
pos++;
}
work(mid + 1, r, pos);
for (int i = k.size() - 1; i >= 0; i--){
del(k[i]);
}
k.clear();
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v >> e[i].w;
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= n; i++){
sz[i] = 1;
fa[i] = i;
}
work(0, e[m].w + 1, 1);
}
所以,这个东西......算了,不多评价吧......