CF757G Can Bash Save the Day? 题解

本文介绍了CF757G问题的解决方案,主要利用树链剖分计算Lca,并通过可持久化线段树维护dfs序中特定区间内Lca的贡献,以解决空间限制问题。文章强调了在处理修改操作时的细节,并提示在空间优化中结构体与数组的权衡。

CF757G Can Bash Save the Day?

时间复杂度分析好题(大雾)。

将询问拆成几部分,也就是 d i s ( u , v ) = d e p ( u ) + d e p ( v ) − 2 ∗ d e p ( L c a ( u , v ) ) dis(u, v) = dep(u) + dep(v) - 2 * dep(Lca(u, v)) dis(u,v)=dep(u)+dep(v)2dep(Lca(u,v))

显然对于 L c a Lca Lca 我们直接进行树链剖分即可。

我们对于每一个 v v v 维护 ≤ l \le l l d e p ( L c a ) dep(Lca) dep(Lca) 的贡献,为了节省空间我们直接使用可持久化线段树,进行插入的时候使用标记永久化。

具体来说就是将 [ l , r ] [l, r] [l,r] 这个 d f s dfs dfs 序的区间进行 + 1 + 1 +1 意味着,整个区间加上了 s u m d e p r − s u m d e p l − 1 sumdep_r - sumdep_{l - 1} sumdeprsumdepl1 的贡献,之后为了保证线段树的复杂度是 log ⁡ n \log n logn 我们直接在这里记录标记了几次。然后在进行查询的时候直接下传还有多少次标记没有传完即可。直接像普通线段树下传标记会出现下传了不是属于当前线段树节点的情况。

对于修改的话,发现前缀和只有一个位置变了,也就是只需要修改一个位置。

然后因为本题是卡空间的,我们考虑一次加入点是 log ⁡ n \log n logn 的,一次插入也是 log ⁡ n \log n logn 的,本质就是一次插入需要 log ⁡ 2 n \log^2n log2n 的空间。根据我们总的线段树空间就可以算出来可以进行直接插入多少次是不会爆空间的。

不然我们就直接重构即可。

**注意:**同样的大小,开结构体占用的空间远大于分开开数组的空间。但是结构体因为是一起开的,所以连续内存访问优化,在时间上会有略微的优势。需要斟酌利弊。

#include <bits/stdc++.h>
using namespace std;

template <typename T>
void r1(T &x) {
	x = 0;
	char c(getchar());
	int f(1);
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(; '0' <= c && c <= '9';c = getchar()) x = (x * 10) + (c ^ 48);
	x *= f;
}

template <typename T,typename... Args> inline void r1(T& t, Args&... args) {
    r1(t);  r1(args...);
}

//#define int long long
const int maxn = 2e5 + 5;
const int maxm = maxn << 1;
typedef long long ll;
bool bg;
const int mod = (1 << 30);

int P[maxn], n, Q;
int head[maxn], fa[maxn], dfn[maxn], top[maxn];
struct Edge {
    int to, next, w;
}edg[maxn << 1];
int cnt(1);
void add(int u,int v,int w) {
    edg[++ cnt] = (Edge) {v, head[u], w}, head[u] = cnt;
}
int son[maxn], siz[maxn], fdfn[maxn], dfntot(0);
ll pre[maxn], sum[maxn], dis[maxn], Edis[maxn];
void dfs(int p,int pre) {
    fa[p] = pre, siz[p] = 1;
    for(int i = head[p];i;i = edg[i].next) {
        int to = edg[i].to; if(to == pre) continue;
        dis[to] = edg[i].w + dis[p];
        Edis[to] = edg[i].w;
        dfs(to, p);
        siz[p] += siz[to];
        if(siz[son[p]] < siz[to]) son[p] = to;
    }
}

void dfs1(int p,int pre,int topf) {
    top[p] = topf, dfn[p] = ++ dfntot, fdfn[dfntot] = p;
    if(!son[p]) return ;
    dfs1(son[p], p, topf);
    for(int i = head[p];i;i = edg[i].next) {
        int to = edg[i].to; if(to == son[p] || to == pre) continue;
        dfs1(to, p, to);
    }
}

int tot(0), Lim;
const int maxT = 37e6;

int tl[maxT], tr[maxT], tag[maxT];

ll val[maxT];
#define ls tl[p]
#define rs tr[p]
#define mid ((l + r) >> 1)
void Insert(int &p,int l,int r,int ll,int rr) {
    if(p <= Lim) {
        ++ tot;
        tl[tot] = tl[p], tr[tot] = tr[p], tag[tot] = tag[p], val[tot] = val[p];
        p = tot;
    }
    if(ll <= l && r <= rr) return ++ tag[p], val[p] += pre[r] - pre[l - 1], void();
    if(ll <= mid) Insert(ls, l, mid, ll, rr);
    if(mid < rr) Insert(rs, mid + 1, r, ll, rr);
    val[p] = val[ls] + val[rs] + (pre[r] - pre[l - 1]) * tag[p];
}
ll Ask(int p,int l,int r,int ll,int rr,int times) {
    if(!p) return times * (pre[min(rr, r)] - pre[max(l - 1, ll - 1)]);
    if(ll <= l && r <= rr) return times * (pre[r] - pre[l - 1]) + val[p];
    times += tag[p];
    long long res(0);
    if(ll <= mid) res += Ask(ls, l, mid, ll, rr, times);
    if(mid < rr) res += Ask(rs, mid + 1, r, ll, rr, times);
    return res;
}
#undef ls
#undef rs
#undef mid

int rt[maxn];

void Insert(int l,int v) {
    Lim = tot;
    while(v) {
        Insert(rt[l], 1, n, dfn[top[v]], dfn[v]);
        v = fa[top[v]];
    }
}

ll Ask(int l,int v) {
    ll ans = sum[l] + dis[v] * l;
    while(v) {
        ans -= Ask(rt[l], 1, n, dfn[top[v]], dfn[v], 0) * 2;
        v = fa[top[v]];
    }
    return ans;
}

bool ed;

signed main() {
//    freopen("S.in", "r", stdin);
//    freopen("S.out", "w", stdout);
//    cerr << (&ed - &bg) / 1024.0 / 1024.0 << endl;
    ll lastans(0);
    int i, j;
    r1(n, Q);
    for(i = 1; i <= n; ++ i) r1(P[i]);
    for(i = 1; i < n; ++ i) {
        int u, v, w;
        r1(u, v, w), add(u, v, w), add(v, u, w);
    }
    dfs(1, 0);
    dfs1(1, 0, 1);

    for(i = 1; i <= n; ++ i) pre[i] = pre[i - 1] + Edis[fdfn[i]];
    for(i = 1; i <= n; ++ i) sum[i] = sum[i - 1] + dis[P[i]];
    for(i = 1; i <= n; ++ i) rt[i] = rt[i - 1], Insert(i, P[i]);

    int Ts(0);

    while(Q --) {
        int opt, l, r, x;
        r1(opt);
        if(opt == 1) {
            r1(l, r, x);
            l = lastans ^ l;
            r = lastans ^ r;
            x = lastans ^ x;
//            printf("l = %d, r = %d, x = %d\n", l, r, x);
            lastans = Ask(r, x) - Ask(l - 1, x);
            printf("%lld\n", lastans);
            lastans %= mod;
        }
        else {
            r1(x);
            x = lastans ^ x;
            swap(P[x], P[x + 1]), sum[x] = sum[x - 1] + dis[P[x]];
            if(++ Ts == 150000) { // 重构
                Ts = 0; tot = 0;
                for(i = 1; i <= n; ++ i) rt[i] = rt[i - 1], Insert(i, P[i]);
            }
            else rt[x] = rt[x - 1], Insert(x, P[x]); // 重新插入,废弃之前
        }
    }
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值