【LG3206】[HNOI2010]城市建设

【LG3206】[HNOI2010]城市建设

题面

洛谷

题解

有一种又好想、码得又舒服的做法叫线段树分治+\(LCT\)

但是因为常数过大,无法跑过此题。

所以这里主要介绍另外一种玄学\(cdq\)分治

对时间进行分治

因为每次分治都必须要缩小数据规模

而我们这里貌似无法满足这个要求

引进了下面的玄学东西:

设当前边集的大小为\(n\),分治区间为\([l,r]\)

则对于分治区间内的边,我们有如下两种剪枝:

\((1)Contraction:\)
将现在所有分治区间内的边权设为\(-\infty\),做一遍最小生成树
那么我们在最小生成树里面的除开边权为\(-\infty\)的边都要选,称这些边为必选边
\((2)Reduction:\)
将现在所有分治区间内的边设为\(\infty\),做一遍最小生成树,
那么此时没有出现在最小生成树中间的边一定不会选到,称为无用边

那么我们每次的无用边、必选边就无需考虑了

我们再用\(cdq\)分治修改序列,就可以达到减小数据规模的目的啦

复杂度网上说是\(O(nlog^2)\),但是不会证啊。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring> 
#include <cmath> 
#include <algorithm>
using namespace std; 
inline int gi() {
    register int data = 0, w = 1;
    register char ch = 0;
    while (!isdigit(ch) && ch != '-') ch = getchar(); 
    if (ch == '-') w = -1, ch = getchar();
    while (isdigit(ch)) data = 10 * data + ch - '0', ch = getchar();
    return w * data; 
} 
typedef long long ll;
const int INF = 1e9; 
const int MAX_N = 5e4 + 5;
struct Mdy { int x, y; } p[MAX_N]; 
struct Edge { int u, v, w, id; } e[18][MAX_N], tmp[MAX_N], stk[MAX_N]; 
inline bool operator < (const Edge &l, const Edge &r) { return l.w < r.w; } 
int N, M, Q, sum[18]; 
int a[MAX_N], c[MAX_N], fa[MAX_N], rnk[MAX_N]; 
ll ans[MAX_N]; 
int getf(int x) { return (x == fa[x]) ? x : fa[x] = getf(fa[x]); }
void unite(int x, int y) { 
    x = getf(x), y = getf(y); 
    if (x == y) return ; 
    if (rnk[x] != rnk[y]) (rnk[x] > rnk[y]) ? fa[y] = x : fa[x] = y; 
    else fa[x] = y, rnk[y]++; 
} 
void Set(int n, Edge *a) { 
    for (int i = 1; i <= n; i++) {
        fa[a[i].u] = a[i].u; 
        fa[a[i].v] = a[i].v; 
        rnk[a[i].v] = rnk[a[i].u] = 1; 
    } 
} 
void Contraction(int &n, ll &val) { 
    int top = 0; 
    Set(n, tmp); sort(&tmp[1], &tmp[n + 1]); 
    for (int i = 1; i <= n; i++) 
        if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
            unite(tmp[i].u, tmp[i].v), stk[++top] = tmp[i]; 
    Set(top, stk); 
    for (int i = 1; i <= top; i++) 
        if (stk[i].w != -INF && getf(stk[i].u) ^ getf(stk[i].v)) 
            val += stk[i].w, unite(stk[i].u, stk[i].v); 
    top = 0; 
    for (int i = 1; i <= n; i++) 
        if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
            stk[++top] = (Edge){getf(tmp[i].u), getf(tmp[i].v), tmp[i].w, tmp[i].id}; 
    for (int i = 1; i <= top; i++) c[tmp[i].id] = i, tmp[i] = stk[i]; 
    n = top; 
} 
void Reduction(int &n) { 
    int top = 0; 
    Set(n, tmp); sort(&tmp[1], &tmp[n + 1]); 
    for (int i = 1; i <= n; i++)
        if (getf(tmp[i].u) ^ getf(tmp[i].v)) 
            unite(tmp[i].u, tmp[i].v), stk[++top] = tmp[i]; 
        else if (tmp[i].w == INF) stk[++top] = tmp[i]; 
    for (int i = 1; i <= top; i++) c[tmp[i].id] = i, tmp[i] = stk[i]; 
    n = top; 
}
void Div(int l, int r, int dep, ll val) {
    int n = sum[dep]; 
    if (l == r) a[p[l].x] = p[l].y; 
    for (int i = 1; i <= n; i++) {
        e[dep][i].w = a[e[dep][i].id]; 
        tmp[i] = e[dep][i], c[tmp[i].id] = i; 
    } 
    if (l == r) {
        ans[l] = val, Set(n, tmp); 
        sort(&tmp[1], &tmp[n + 1]);
        for (int i = 1; i <= n; i++) 
            if (getf(tmp[i].u) != getf(tmp[i].v))
                unite(tmp[i].u, tmp[i].v), ans[l] += tmp[i].w; 
        return ; 
    } 
    for (int i = l; i <= r; i++) tmp[c[p[i].x]].w = -INF; 
    Contraction(n, val); 
    for (int i = l; i <= r; i++) tmp[c[p[i].x]].w = INF; 
    Reduction(n); 
    for (int i = 1; i <= n; i++) e[dep + 1][i] = tmp[i]; 
    sum[dep + 1] = n;
    int mid = (l + r) >> 1; 
    Div(l, mid, dep + 1, val); 
    Div(mid + 1, r, dep + 1, val); 
} 
int main () {
    N = gi(), M = gi(), Q = gi(); 
    for (int i = 1; i <= M; i++) e[0][i] = (Edge){gi(), gi(), a[i] = gi(), i}; 
    for (int i = 1; i <= Q; i++) p[i] = (Mdy){gi(), gi()};
    sum[0] = M; Div(1, Q, 0, 0); 
    for (int i = 1; i <= Q; i++) printf("%lld\n", ans[i]); 
    return 0; 
} 

转载于:https://www.cnblogs.com/heyujun/p/10333943.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值