《信息学奥赛一本通·高手专项训练》集训 Day 10
luckydrawbox · · 个人记录
并查集
\color{#FFC116}\text{A. 最低热量}
题目
小明将学校中的所有地点编号为
学校中有
在经过的所有道路中最高温度最低的前提下,使小明到达终点时的热量最低(从起点出发时,小明的热量为
题解
我们先考虑前提——最高温度最低,但必须满足
然后直接跑最短路求答案即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 5e5 + 10, M = 1e6 + 10;
int n, m, S, T, l, r;
int head[N], ver[M << 1], nxt[M << 1], te[M << 1], tot = 1;
ll ce[M << 1], d[N], v[N], ans;
int fa[N];
vector<int> e[N];
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
void add(int a, int b, int t, ll c) {
ver[++tot] = b;
te[tot] = t;
ce[tot] = c;
nxt[tot] = head[a];
head[a] = tot;
}
void Dijkstra(int mid) {
memset(d, 0x3f, sizeof(d));
memset(v, 0, sizeof(v));
priority_queue<pair<ll, int> > q;
q.push(make_pair(0, S));
d[S] = 0;
while (q.size()) {
int x = q.top().second;
q.pop();
if (v[x])
continue;
v[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
if (te[i] > mid)
continue;
int y = ver[i];
ll cost = (ll)te[i] * ce[i];
if (d[y] > d[x] + cost) {
d[y] = d[x] + cost;
q.push(make_pair(-d[y], y));
}
}
}
ans = d[T];
}
int main() {
freopen("running.in", "r", stdin);
freopen("running.out", "w", stdout);
n = read();
m = read();
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
int a, b, t;
ll c;
a = read();
b = read();
t = read();
c = read();
add(a, b, t, c);
add(b, a, t, c);
r = max(r, t);
e[t].push_back(tot);
}
S = read();
T = read();
for (l = 0; l <= r; l++) {
for (int j = 0; j < e[l].size(); j++) {
fa[get(ver[e[l][j]])] = get(ver[e[l][j] ^ 1]);
}
if (get(S) == get(T))
break;
}
Dijkstra(l);
write(l);
putchar(' ');
write(ans);
return 0;
}
\color{#52C41A}\text{B. 一道树论}
题目
小 W 现在有一个点集
但是小 W 很好奇,如果他想要断掉一些边,使得点集
他把这个问题丢给了你,想让你来回答,你只用告诉他要断的边的边权和是多少即可。
但他又觉得太简单了,所以他会一个个把
题解
删除边不好维护,我们可以把顺序倒过来改成加边,用并查集维护点的连通性。
为了使删除的边的边权和最小,反过来就要使加入的边的边权和最大,因此将边从大到小排序,逐条考虑。
对于一条要加入的边
最后通过边加入的时间计算答案即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 5e5 + 10;
int n, a[N], fa[N], ti[N], d[N], mx[N];
int head[N], ver[N << 1], nxt[N << 1], v[N << 1], tim[N << 1], tot = 1;
ll edge[N << 1], ans[N];
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
void add(int x, int y, ll z) {
ver[++tot] = y;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
bool cmp(int x, int y) { return edge[2 * x] > edge[2 * y]; }
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
n = read();
for (int i = 1; i < n; i++) {
int x, y;
ll z;
x = read();
y = read();
z = read();
add(x, y, z);
add(y, x, z);
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
ti[a[i]] = i;
fa[i] = i;
d[i] = i;
}
sort(d + 1, d + n, cmp);
for (int i = 1; i < n; i++) {
int u = get(ver[d[i] * 2]), v = get(ver[d[i] * 2 + 1]);
tim[d[i]] = max(ti[u], ti[v]);
ti[u] = min(ti[u], ti[v]);
fa[v] = u;
ans[tim[d[i]]] += edge[d[i] * 2];
}
for (int i = 1; i <= n; i++) {
ans[i] += ans[i - 1];
write(ans[i]);
putchar('\n');
}
return 0;
}
\color{#52C41A}\text{C. 树上除法}
题目
给出一棵包含
\color{#52C41A}\text{A. 路径统计}
题目
给出一个包含
- 定义一条路径的价值为路径上最小边的权值。
- 定义
\text{dist}(i,j) 为起点为i ,终点为j 的所有路径中,价值最大的路径的价值。
现在,给出一个
题解
为了使路径价值最大,我们求该图的最大生成树,建出其
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5 + 10, M = 4e5 + 10;
int n, m, fa[N], tot, dist[N];
int h1[N], v1[M << 1], n1[M << 1], e1[M << 1], t1 = 1;
ll k, sl[N], sr[N], sze[N];
void add1(int x, int y, int z) {
v1[++t1] = y;
e1[t1] = z;
n1[t1] = h1[x];
h1[x] = t1;
}
void init() {
for (int i = 1; i <= 2 * n; i++) fa[i] = i;
for (int i = 1; i <= n; i++) sze[i] = 1;
}
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
struct Kruskal_rebuild {
pair<int, pair<int, int> > a[M];
int m;
void ask(int n) {
m = 0;
tot = n;
init();
for (int i = 1; i <= n; i++) {
for (int j = h1[i]; j; j = n1[j]) {
int y = v1[j], z = e1[j];
if (i < y)
a[++m] = (make_pair(z, make_pair(i, y)));
}
}
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++) {
int u = a[i].second.first;
int v = a[i].second.second;
if (get(u) != get(v)) {
int w = ++tot;
sze[w] = sze[get(u)] + sze[get(v)];
sl[w] = sze[get(u)];
sr[w] = sze[get(v)];
fa[get(u)] = w;
fa[get(v)] = w;
dist[w] = -a[i].first;
}
}
}
} tu;
int main() {
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
n = read();
m = read();
k = ((ll)n) * ((ll)n - 1) / 2 - read();
for (int i = 1; i <= m; i++) {
int x, y, z;
x = read();
y = read();
z = -read();
add1(x, y, z);
add1(y, x, z);
}
tu.ask(n);
for (int i = tot; i > n; i--) {
k -= (ll)sl[i] * (ll)sr[i];
if (k < 0) {
cout << dist[i];
return 0;
}
}
return 0;
}
\color{#3498DB}\text{B. 黑白之树}
题目
给出一个包含
请你求一棵最小权的恰好有
题解
P2619 [国家集训队]Tree I
设
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 5e4 + 10, M = 1e5 + 10;
int n, m, k, fa[N], ans;
int head[N], ver[M << 1], nxt[M << 1], col[M << 1], edge[M << 1], tot;
void add(int x, int y, int c, int z) {
ver[++tot] = y;
col[tot] = c;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
void init() {
for (int i = 1; i <= n; i++) fa[i] = i;
}
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
void merge(int x, int y) { fa[get(x)] = get(y); }
struct Kruskal {
pair<pair<int, int>, pair<int, int> > a[M];
int m, ans, cnt;
void ask(int n, int mid) {
m = ans = cnt = 0;
init();
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j; j = nxt[j]) {
int y = ver[j], c = col[j], z = edge[j];
if (i < y)
a[++m] = (make_pair(make_pair(z + c * mid, c), make_pair(i, y)));
}
}
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++) {
int u = a[i].second.first;
int v = a[i].second.second;
if (get(u) != get(v)) {
merge(u, v);
cnt += a[i].first.second;
ans += a[i].first.first;
}
}
}
} tu;
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
n = read();
m = read();
k = read();
for (int i = 1; i <= m; i++) {
int x, y, c, z;
x = read() + 1;
y = read() + 1;
z = read();
c = 1 - read();
add(x, y, c, z);
add(y, x, c, z);
}
int l = -101, r = 101;
while (l < r) {
int mid = (l + r) >> 1;
tu.ask(n, mid);
if (tu.cnt <= k) {
ans = tu.ans - k * mid;
r = mid;
} else
l = mid + 1;
}
cout << ans << endl;
return 0;
}
\color{#3498DB}\text{C. 生成树约数}
题目
给出一个包含
题解
如果我们把所有边权都唯一分解,那么可以发现每个质数对答案的贡献是互不干预的,于是我们可以对每个质数分开计算,这样,公约数和公倍数的计算就变成了对质数因子个数的取
那么对于质数
而最大生成树一定是最大瓶颈生成树,所以对于每个质数求相应的最大生成树再计算贡献即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e3 + 10, M = 1e5 + 10;
int n, m, fa[N];
vector<pair<ll, pair<int, int> > > e[M];
ll np, prime[M], fac[M], ans = 1;
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
void sieve() {
for (int i = 2; i < (1 << 15); i++) {
if (!fac[i]) {
fac[i] = i;
prime[++np] = i;
}
for (int j = 1; j <= np; j++) {
if (prime[j] > fac[i] || prime[j] * i + 1 > (1 << 15))
break;
fac[prime[j] * i] = prime[j];
}
}
}
ll qmi(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1)
ans *= a;
a *= a;
b >>= 1;
}
return ans;
}
struct Kruskal {
int m;
void ask(int n, ll y) {
m = 0;
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
sort(e[prime[y]].begin(), e[prime[y]].end());
for (int i = 0; i < e[prime[y]].size(); i++) {
int u = e[prime[y]][i].second.first;
int v = e[prime[y]][i].second.second;
if (get(u) != get(v)) {
fa[get(u)] = get(v);
m++;
if (m == n - 1) {
ans *= qmi(prime[y], -e[prime[y]][i].first);
break;
}
}
}
}
} tu;
int main() {
freopen("divisor.in", "r", stdin);
freopen("divisor.out", "w", stdout);
n = read();
m = read();
sieve();
for (int i = 1; i <= m; i++) {
int x, y;
ll z;
x = read();
y = read();
z = read();
for (ll j = 1; prime[j] * prime[j] <= z; j++)
if (z % prime[j] == 0) {
ll cnt = 0;
while (z % prime[j] == 0) {
z /= prime[j];
cnt++;
}
e[prime[j]].push_back(make_pair(-cnt, make_pair(x, y)));
}
if (z > 1)
e[z].push_back(make_pair(-1, make_pair(x, y)));
}
for (int i = 1; i <= np; i++) tu.ask(n, i);
write(ans);
return 0;
}