倍增

Minimum spanning tree for each edge

求出一颗最小生成树, 然后由于我们必须包括第 i 条边

假设这条边在最小生成树上就是最小生成树的权值和

反之, 我们需要加入这条边到最小生成树上, 我们假设这条边连接  u, v 两条边, 那么我们贪心的去拿最小生成树上最长边断开, 加入该边

最小生成树上两点路径最大值可以在求 lca 时顺便倍增维护

#include <bits/stdc++.h>
using namespace std;
#define endl "\n";
#define int long long
typedef long long ll;

const int M = 2e5 + 100;
const int N = 2e5 + 100;

struct Node {
    int x, y, v, id;
} edge[M];

int n, m, fa[N], vis[N];
vector<array<int, 2>> g[N];

int findset(int i) {
    if (i == fa[i]) return i;
    return fa[i] = findset(fa[i]);
}

bool cmp(Node a, Node b) {
    return a.v < b.v;
}

ll Kruskal() {
    for (int i = 1; i <= n; i++) fa[i] = i;
    sort(edge + 1, edge + 1 + m, cmp);
    ll ans = 0, cnt = n;
    for (int i = 1; i <= m; i++) {
        auto [x, y, v, id] = edge[i];
        int fx = findset(x);
        int fy = findset(y);
        if (fx != fy) {
            fa[fx] = fy;
            ans += v;
            g[x].push_back({y, v});
            g[y].push_back({x, v});
            vis[id] = 1;
            cnt--;
        }
        if (cnt == 1) break;
    }
    return ans;
}

int father[N][25], f[N][25], dist[N];
ll res[N];

void dfs(int x, int fa) {
    for (auto [y, v] : g[x]) {
        if (y == fa) continue;
        dist[y] = dist[x] + 1;
        father[y][0] = x;
        f[y][0] = v;
        dfs(y, x);
    }
}

int query(int x, int y) {
    if (dist[x] < dist[y]) swap(x, y);
    int z = dist[x] - dist[y];
    int ans = 0;
    for (int i = 0; i <= 20 && z; i++, z >>= 1) {
        if (z & 1) ans = max(ans, f[x][i]), x = father[x][i];
    }
    if (x == y) return ans;
    for (int i = 20; i >= 0; i--) {
        if (father[x][i] != father[y][i]) ans = max({f[x][i], f[y][i], ans}), x = father[x][i], y = father[y][i];
    }
    ans = max(ans, f[x][0]);
    ans = max(ans, f[y][0]);
    return ans;
}

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int x, y, v; cin >> x >> y >> v;
        edge[i] = {x, y, v, i};
    }

    ll sum = Kruskal();
    dist[1] = 1;
    dfs(1, 1);

    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j <= n; j++) {
            f[j][i] = max(f[j][i - 1], f[father[j][i - 1]][i - 1]);
            father[j][i] = father[father[j][i - 1]][i - 1];
        }
    }

    for (int i = 1; i <= m; i++) {
        auto [x, y, v, id] = edge[i];
        if (vis[id]) res[id] = sum;
        else res[id] = sum + v - query(x, y);
    }

    for (int i = 1; i <= m; i++) cout << res[i] << endl;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

    // int T; cin >> T;
    // while (T--) solve();
    solve();

    return 0;
}
View Code

P1967 [NOIP2013 提高组]

和上题差不多

Analysis of Pathes in Functional Graph

和上面两题都差不多,只是维护东西稍微不同

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll; 

const int N = 1e5 + 100;

int f[N][40], g[N][40], v[N][40];

void query(int x, int k) {
    int ans = LLONG_MAX, res = 0;
    for (int i = 0; i <= 40 && k; i++, k >>= 1) {
        if (k & 1) {
            ans = min(v[x][i], ans);
            res = res + g[x][i];
            x = f[x][i];
        } 
    }
    cout << res << ' ' << ans << endl;
}

void solve() {    
    int n, k; cin >> n >> k;

    memset(v, 127, sizeof(v));

    for (int i = 1; i <= n; i++) cin >> f[i][0], f[i][0]++;
    for (int i = 1; i <= n; i++) cin >> g[i][0], v[i][0] = g[i][0];

    for (int i = 1; i <= 40 && 1ll << i <= k; i++) {
        for (int j = 1; j <= n; j++) {
            if (f[j][i - 1]) {
                f[j][i] = f[f[j][i - 1]][i - 1];
                v[j][i]    = min(v[j][i - 1], v[f[j][i - 1]][i - 1]);
                g[j][i] = g[j][i - 1] + g[f[j][i - 1]][i - 1];        
            }
        }
    }

    for (int i = 1; i <= n; i++) query(i, k);
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

    // int T = 1; cin >> T;
    // while(T--) solve();
    solve();

    return 0;
}
View Code

Cut

很有意思的一题

首先我们知道倍增是维护一个东西走 2 ^ i 步的状态

对于这题 我们知道划分出来每一段的数质因子都只能出现一次, 我们根据这个性质贪心, 就可以得到每划一次能到的最远点

这时我们就可以把区间 [ L , R ] 的查询转换成从 L 走 x 次到 R, 倍增去跳就行

这里 f [ i ][ j ] 维护的是 i 跳 2 ^ j 步到的点的下一个点

#include <bits/stdc++.h>
using namespace std;
#define endl "\n";
typedef long long ll;

const int N = 1e5 + 100;

int a[N], f[N][25], pos[N];
vector<int> Div[N];
bool primes[N];

void init() {
    for (int i = 2; i <= N - 100; i++) {
        if (!primes[i]) {
            Div[i].push_back(i);
            for (int j = i + i; j <= N - 100; j += i) {
                primes[j] = true;
                Div[j].push_back(i);
            }
        }
    }
}

int query(int l, int r) {
    int ans = 0;
    for (int i = 20; i >= 0; i--) {
        if (f[l][i] <= r) {
            ans += (1 << i);
            l = f[l][i];
        }
    }
    return ans + 1;
}

void solve() {
    int n, q; cin >> n >> q;
    for (int i = 1; i <= n; i++) cin >> a[i];
    f[n + 1][0] = n + 1;
    memset(pos, 127, sizeof(pos));
    for (int i = n; i >= 1; i--) {
        f[i][0] = f[i + 1][0];
        for (auto j : Div[a[i]]) {
            f[i][0] = min(pos[j], f[i][0]);
            pos[j] = i;
        }
    }
    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j <= n; j++) {
            if (f[j][i - 1] <= n) f[j][i] = f[f[j][i - 1]][i - 1];
            else f[j][i] = n + 1;
        }
    }

    while (q--) {
        int l, r; cin >> l >> r;
        cout << query(l, r) << endl;
    }
}


int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    // int T; cin >> T;
    // while (T--) solve();
    solve();

    return 0;
}
View Code

 

posted @ 2024-01-16 22:27  zhujio  阅读(6)  评论(0编辑  收藏  举报