25年CSP前ds做题记录

ODT

小清新题,考虑每个点只维护轻儿子的信息,查询的时候加入自己和父亲再查。修改就跳重链暴力改即可。

inline void mdft(int u, int v, int val){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]])swap(u, v); a[top[u]] = qry(dfn[top[u]]); del(tr[fa[top[u]]], a[top[u]], top[u]);
        mdf(dfn[top[u]], dfn[u], val); ins(tr[fa[top[u]]], a[top[u]] + val, top[u]); u = fa[top[u]];
    }
    if(dep[u] > dep[v])swap(u, v); if(top[u] == u and fa[u])
        a[u] = qry(dfn[u]), del(tr[fa[u]], a[u], u), ins(tr[fa[u]], a[u] + val, u);
    mdf(dfn[u], dfn[v], val);
}

signed main(){
    n = rd(), q = rd(); for(int i = 1; i <= n; ++i)a[i] = rd();
    for(int i = 1, u, v; i < n; ++i)u = rd(), v = rd(), add(u, v), add(v, u);
    dfs1(1, 0); dfs2(1, 1); for(int i = 1; i <= n; ++i)mdf(dfn[i], dfn[i], a[i]);
    for(int o, x, y; q--; ){
        o = rd(), x = rd(), y = rd();
        if(o < 2)mdft(x, y, rd());
        else{
            pii res; a[x] = qry(dfn[x]); ins(tr[x], a[x], x);
            if(son[x])a[son[x]] = qry(dfn[son[x]]), ins(tr[x], a[son[x]], son[x]);
            if(fa[x])a[fa[x]] = qry(dfn[fa[x]]), ins(tr[x], a[fa[x]], fa[x]);
            res = kth(tr[x], y); wt(res.first); pc('\n'); del(tr[x], a[x], x);
            if(son[x])del(tr[x], a[son[x]], son[x]); if(fa[x])del(tr[x], a[fa[x]], fa[x]);
        }
    }
    return 0;
}

Lamborghini

我们考虑一个点 \(x\) 成为 \(f(i,j)\) 的贡献,容易想到按 \(t_x\) 从大到小排序加入树中,这样我们只需要在每次加入一个点的时候考虑其贡献。首先因为是每次加点所以并查集维护连通块,其次因为每次加点后我们统计的是合法路径的数量,而满足合法只对两个 \(v_i\) 各有一个限制,并且是在不同子树中。于是你就再用一个权值线段树维护每个连通块的 \(v_i\),然后每次做一个线段树合并即可。

signed main(){
    n = rd(); for(int i = 1; i <= n; ++i)a[i] = rd(), ff[i] = id[i] = i;
    for(int i = 1; i <= n; ++i)m = max(m, b[i] = rd());
    sort(id + 1, id + 1 + n, [](int x, int y){return a[x] > a[y];});
    for(int i = 1, u, v; i < n; ++i)u = rd(), v = rd(), add(u, v), add(v, u);
    for(int x = 1; x <= n; ++x){
        int u = id[x]; mdf(rt[u], 1, m, b[u]); ans += u;
        for(int i = hd[u], v; i; i = e[i].nxt)if(a[v = fd(e[i].to)] > a[u]){
            ans += 1ll * u * qry(rt[u], 1, m, 1, b[u]) * qry(rt[v], 1, m, b[u], m);
            ans += 1ll * u * qry(rt[v], 1, m, 1, b[u]) * qry(rt[u], 1, m, b[u], m);
            ff[v] = u; rt[u] = mrg(rt[u], rt[v], 1, m);
        }
    }
    return wt(ans), 0;
}

youyou 的三进制数

首先可以图论建模,没想到可以退役了。然后题意就相当于是对于每个起点终点,都会走出若干路径,但是这些路径肯定都会过一些固定点(比如起点终点)。现在要对每个 \(z\) 计算有多少合法点对 \((x,y)\),合法指的是过 \(x,y\) 的任意路径都与以 \(z\) 为端点的路径有一个交点。因为交点是一个定点,相当于我两个点的路径一定经过这个点,所以他是一个割点。于是我们可以想到对图建圆方树,然后在每个圆点上考虑贡献。因为 \(x,y,z\) 不在同一个子树中且三点等价,于是就记录每个交点对其他点的贡献即可。

inline void dfs1(int u, int fa){
    sz[u] = u <= n; ll res = 0;
    for(int i = hd[u], v; ~ i; i = e[i].nxt)if((v = e[i].to) != fa)
        dfs1(v, u), res += sz[u] * sz[v], sz[u] += sz[v];
    if(u > n)return; f[0] += res; f[u] += sz[u] * (n + 1 - sz[u]);
    for(int i = hd[u], v; ~ i; i = e[i].nxt)if((v = e[i].to) != fa)
        f[v] -= sz[v] * (n + 1 - sz[v]);
}
inline void dfs2(int u, int fa){for(int i = hd[u], v; ~ i; i = e[i].nxt)if((v = e[i].to) != fa)f[v] += f[u], dfs2(v, u);}

signed main(){
    tot = n = rd(), lim = rd(); b[0] = 1; memset(hd, - 1, sizeof hd);
    for(int i = 1; i <= n; ++i)
        g[i / 3].push_back(i), g[i].push_back(i / 3),
        b[i] = b[i / 3] * 3;
    for(int i = 1; i <= lim; ++i)if(i % 3 > 0){
        int j = (i % 3) * b[i / 3] + i / 3;
        if(j <= n)g[i].push_back(j), g[j].push_back(i);
    }
    tar(0); dfs1(0, - 1); dfs2(0, - 1);
    for(int i = 0; i <= n; ++i)wt(f[i] << 1), pc('\n');
    return 0;
}

機械生命体

假如没有三操作那就很好了。有操作三我们考虑去拆解它,这个东西是对 trie 的一棵子树加,但是我们只会全局加 1,于是考虑类比。首先如果当前 v 是奇数我们做一次全局加一并且 --v,然后对于 v 是偶数的时候,我们发现它对当前的点无影响,于是就传到子树当中去做。我们多给每个点记一个懒标记即可,注意平移后可能会出现有重叠的部分,所以还要一个 trie 合并。

inline void pu(int x){sz[x] = sz[ls] + sz[rs];}
inline void pd(int x){
    if(! tg[x])return; if(tg[x] & 1)++tg[rs], swap(ls, rs);
    tg[ls] += tg[x] >> 1; tg[rs] += tg[x] >> 1; tg[x] = 0;
}
inline void mdf(int &x, int dep, ui y, int z){
    if(! x)x = ++nd; sz[x] += z;if(dep == M)return;
    pd(x); mdf(ch[x][y >> dep & 1], dep + 1, y, z);
}
inline int mrg(int x, int y, int dep){
    if(! x or ! y)return x | y; if(dep == M)return sz[x] += y, x;
    pd(x); pd(y); ls = mrg(ls, ch[y][0], dep + 1);
    rs = mrg(rs, ch[y][1], dep + 1); return pu(x), x;
}
inline void opt(int &x, int &las, int dep, ui y, ui z, int k){
    if(! las)return; if(! x)x = ++nd; if(dep == k)
        {int t = las; tg[t] += y + z >> k; las = 0; x = mrg(x, t, k); return;}
    pd(x); pd(las); opt(ch[x][y + z >> dep & 1], ch[las][y >> dep & 1], dep + 1, y, z, k);
    pu(x); pu(las);
}
inline ui qry(int x, int dep, ui y){
    if(dep == M)return M; pd(x); int c = y >> dep & 1;
    if(sz[ch[x][c]])return qry(ch[x][c], dep + 1, y);
    return dep;
}

Countless J-Light Decomposition

首先考虑 dp:

\[f_u=\max_{(i+1)th}\{f_v+e(u,v)\} \]

考虑当 \(i\ge d_u\) 的时候 u 就没用了,所以我们可以先建虚树再 dp,考虑每个点在虚树中只会出现 \(d_u\) 次,均摊是 \(\mathcal O(n)\) 的。然后对每个点维护一个平衡树记录虚树上 \(f_v+e(u,v)\),这个同理可以均摊。

inline void dfs1(int u, int fa){
    dep[u] = dep[fa] + 1; dfn[u] = ++tim; ff[u][0] = fa;
    for(int i = 1; i < 20; ++i)ff[u][i] = ff[ff[u][i - 1]][i - 1];
    for(auto i : g[u])if(i.first != fa){
        tr[u].insert(make_pair(i.second, i.first));
        a[i.first] = i.second; dfs1(i.first, u); ++d[u];
    }
}
inline int get(int u, int v){for(int i = 19; ~ i; --i)if(dep[ff[u][i]] > dep[v])u = ff[u][i]; return u;}
inline int getlca(int u, int v){
    if(dep[u] < dep[v])swap(u, v);
    for(int i = 19; ~ i; --i)if(dep[ff[u][i]] >= dep[v])u = ff[u][i];
    if(u == v)return u; for(int i = 19; ~ i; --i)if(ff[u][i] != ff[v][i])
        u = ff[u][i], v = ff[v][i]; return ff[u][0];
}
inline void dfs2(int u, int k){
    f[u] = 0; for(auto i : ig[u])dfs2(i.first, k), f[u] = max(f[u], f[i.first]);
    if(d[u] <= k)return; for(auto i : ig[u])
        tr[u].erase(make_pair(a[i.second], i.second)), tr[u].insert(make_pair(f[i.first] + a[i.second], i.second));
    f[u] = max(f[u], tr[u].find_by_order(k) -> first);
    for(auto i : ig[u])
        tr[u].erase(make_pair(f[i.first] + a[i.second], i.second)), tr[u].insert(make_pair(a[i.second], i.second));
}
inline ll calc(vector < int > &id, int k){
    sort(id.begin(), id.end(), [](int x, int y){return dfn[x] < dfn[y];});
    for(int i = 1, m = id.size(); i < m; ++i)id.push_back(getlca(id[i], id[i - 1]));
    sort(id.begin(), id.end(), [](int x, int y){return dfn[x] < dfn[y];});
    id.erase(unique(id.begin(), id.end()), id.end());
    for(int i = 1, m = id.size(); i < m; ++i){
        int lca = getlca(id[i - 1], id[i]);
        ig[lca].push_back(make_pair(id[i], get(id[i], lca)));
    }
    dfs2(1, k); for(int i : id)vector < pii > ().swap(ig[i]); return f[1];
}

Japanese Lottery

考虑一个修改带来的影响,其实就是交换相邻的两个签,再结合 \(w\le20\) 这一点我们可以想到直接维护一个置换的形态。现在考虑知道一个置换后我们怎么最快还原?我们考虑最开始有 \(m\) 个置换环,最后有 \(n\) 个置换环,那么实际上我们的目标等价于进行一些删除操作使置换环数量增加,直到总数变为 \(n\) 的最少步数。考虑当 \(m<n\) 的时候一定存在 \(i<j,p_i>p_j\),为什么会有这个呢?考虑我们一定存在一次交换 \(p_i,p_j\) 的操作,因为是相邻交换,不然就不能出现前面的情况。所以我们删掉这条边就能让环数加一,所以答案有一个下界是 \(n-m\)。现在考虑是否存在更优的操作,答案是否定的,因为如果对于一个置换里面的操作不用上述做法一定更劣,对于两个置换环操作我们反而会让环数减一,所以不存在更优的结果,于是我们就只用维护置换形态,每次查询暴力统计置换环数量即可。

int n, m, q, op[N], m1, m2;
struct perm{int A[25]; inline int & operator [](int x){return A[x];}}tr[N << 2];
inline perm operator * (perm a, perm b){perm c; for(int i = 1; i <= n; ++i)c[i] = b[a[i]]; return c;}
inline void init(perm &a){for(int i = 1; i <= n; ++i)a[i] = i;}
inline int get(perm a){
    int s = 0, vs[22]; for(int i = 1; i <= n; ++i)vs[i] = 0;
    for(int i = 1; i <= n; ++i)if(! vs[i]){
        ++s; while(! vs[i])vs[i] = s, i = a[i];
    }
    return s;
}

#define ls x << 1
#define rs ls | 1
#define lson ls, l, mid
#define rson rs, mid + 1, r

inline void pu(int x){tr[x] = tr[ls] * tr[rs];}
inline void bd(int x, int l, int r){
    init(tr[x]); if(l == r)return;
    int mid = l + r >> 1; bd(lson); bd(rson);
}
inline void mdf(int x, int l, int r, int pos, bool o){
    if(l == r){
        if(o)swap(tr[x][m1], tr[x][m2]);
        else init(tr[x]);
        return;
    } int mid = l + r >> 1;
    pos <= mid ? mdf(lson, pos, o) : mdf(rson, pos, o); pu(x);
}

const string FileName = "kon";
signed main(){
    // fileio(FileName);
    n = rd(), m = rd(), q = rd(); bd(1, 1, m);
    for(int i = 1, h; i <= q; ++i){
        h = rd(), m1 = rd(), m2 = rd();
        mdf(1, 1, m, h, op[h] ^= 1);
        wt(n - get(tr[1])); pc('\n');
    }
    return 0;
}

树咖 / Arboras

对于每个点我们要找最长和次长的链,可以想到长剖,然后在轻儿子中选一个最长的。于是我们尝试去维护长剖的形态。考虑一次修改操作会影响一条树链的信息,首先从被修改的边 \((u,v)\) 的深度较小的点 u 开始,一定会有一段虚边变成实边,然后还有一堆区间加的操作,以及树链改变时维护轻儿子信息的 ds 要修改。

维护链顶可以树剖后对每个重链用 set 维护链顶位置,区间加直接树剖树状数组。还有就是每个点开一个 set 维护轻儿子。

inline void dfs(int u){
    dep[u] = dep[ff[u][0] = fa[u]] + 1; sz[u] = 1;
    dfn[u] = ++tim; for(int i = 1; i < 20; ++i)ff[u][i] = ff[ff[u][i - 1]][i - 1];
    for(int v : e[u])dfs(v), sz[u] += sz[v], ss[u].insert(make_pair(a[v] + f[v], v));
    if(ss[u].empty())return; son[u] = (--ss[u].end()) -> second;
    ss[u].erase(--ss[u].end()); f[u] = f[son[u]] + a[son[u]];
    ADD(ans, Mo(f[u])); if(! ss[u].empty())ADD(ans, Mo((--ss[u].end()) -> first));
    for(auto i : ss[u])ist[i.second] = true;
}
inline void mdf1(int x, ll y){if(! x)return; for(; x <= n; x += x & - x)tr[x] += y;}
inline void mdf2(int x, int y){if(! x)return; for(; x <= n; x += x & - x)t[x] += y;}
inline void mdf(int l, int r, int x){mdf2(l, x); mdf2(r + 1, - x);}
inline ll qry1(int x){ll s = 0; for(; x; x ^= x & - x)s += tr[x]; return s;}
inline ll qry(int l, int r){return qry1(r) - qry1(l - 1);}
inline int qry2(int x){int s = 0; for(; x; x ^= x & - x)s += t[x]; return s;}
inline int jp(int u){
    int x = qry2(dfn[u]); for(int i = 19; ~ i; --i)
        if(ff[u][i] and qry2(dfn[ff[u][i]]) == x)u = ff[u][i];
    return u;
}
inline int upd(int u, ll val){
    int top = jp(u); ADD(ans, Mul(Mo(val), dep[u] - dep[top]));
    ms.push_back(make_pair(val, dfn[fa[u]]));
    ms.push_back(make_pair(- val, dfn[fa[top]])); return top;
}

inline void sol(int u, ll val){
    vector < pii > ().swap(ms);
    for(ll x = upd(u, val), y = val; x != 1; x = upd(x, y)){
        ll tmp = f[x] + a[x] + qry(dfn[x], dfn[x] + sz[x] - 1), aim = f[fa[x]] + qry(dfn[fa[x]], dfn[fa[x]] + sz[fa[x]] - 1);
        if(tmp + y <= aim){
            SUB(ans, Mo((--ss[fa[x]].end()) -> first)); ss[fa[x]].erase(make_pair(tmp, x));
            ss[fa[x]].insert(make_pair(tmp + y, x)); ADD(ans, Mo((--ss[fa[x]].end()) -> first));
            break;
        }
        SUB(ans, Mo((--ss[fa[x]].end()) -> first)); ss[fa[x]].erase(make_pair(tmp, x));
        ss[fa[x]].insert(make_pair(aim, son[fa[x]])); ADD(ans, Mo((--ss[fa[x]].end()) -> first));
        mdf(dfn[son[fa[x]]], dfn[son[fa[x]]] + sz[son[fa[x]]] - 1, 1); ist[son[fa[x]]] = true;
        mdf(dfn[x], dfn[x] + sz[x] - 1, - 1); ist[x] = false; son[fa[x]] = x; y += tmp - aim;
    }
    a[u] += val; for(auto i : ms)mdf1(i.second, i.first);
}

const string FileName = "tree";
signed main(){
    // fileio(FileName);
    n = rd();
    for(int i = 2; i <= n; ++i)fa[i] = rd(), e[++fa[i]].push_back(i);
    for(int i = 2; i <= n; ++i)a[i] = rd(); dfs(1);
    for(int i = 2; i <= n; ++i)if(ist[i])mdf(dfn[i], dfn[i] + sz[i] - 1, 1);
    wt(ans); pc('\n'); q = rd(); for(int i = 1, u, w; i <= q; ++i)
        u = rd() + 1, w = rd(), sol(u, w), wt(ans), pc('\n');
    return 0;
}

成都七中

树上连通性考虑淀粉质。然后可以发现树上的任意一个连通块中,存在一个在点分树上深度最小的节点,并且整个连通块都在这个节点的子树中。为啥是对的?考虑反证法,如果有一个点不在这个最浅点的子树中那么连通块和这个点之间的联通路径一定经过一个深度更浅的点,矛盾。

每次把询问挂到最浅点后我们考虑 dfs 子树的每个点,对于每个点到根的路径我们处理出编号的最值,这是一个二维信息,于是我们就需要在二维平面上做查询了。于是考虑扫描线干掉一维,ds 维护一维,对每个颜色记录可以贡献的区间即可。

inline void fdrt(int u, int fa, int tot){
    ma[u] = 0; sz[u] = 1;
    for(int v : e[u])if(v != fa and ! vs[v])
        fdrt(v, u, tot), sz[u] += sz[v], ma[u] = max(ma[u], sz[v]);
    ma[u] = max(ma[u], tot - sz[u]); if(ma[u] < ma[rt])rt = u;
}
inline void dfss(int u, int ff, int lp = inf, int rp = - inf){
    lp = min(lp, u); rp = max(rp, u); sz[u] = 1;
    fa[u].push_back({lp, rp, rt}); qs[rt].push_back({lp, rp, col[u], false});
    for(int v : e[u])if(v != ff and ! vs[v])dfss(v, u, lp, rp), sz[u] += sz[v];
}
inline void dfs(int u){
    vs[u] = true; dfss(u, 0); for(int v : e[u])
        if(! vs[v])rt = 0, fdrt(v, u, sz[v]), dfs(rt);
}

signed main(){
    n = rd(), q = rd(); ma[0] = inf;
    for(int i = 1; i <= n; ++i)col[i] = rd(), rp[i] = inf;
    for(int i = 1, u, v; i < n; ++i)u = rd(), v = rd(),
        e[u].push_back(v), e[v].push_back(u);
    fdrt(1, 0, n); dfs(rt);
    for(int i = 1, l, r, x; i <= q; ++i){
        l = rd(), r = rd(), x = rd();
        for(auto j : fa[x])if(l <= j.l and j.r <= r){
            qs[j.x].push_back({l, r, i, true}); break;
        }
    }
    for(int i = 1; i <= n; ++i){
        sort(qs[i].begin(), qs[i].end(), cmp);
        for(auto j : qs[i])if(j.o)ans[j.x] = qry(j.r);
            else if(rp[j.x] > j.r){if(rp[j.x] != inf)mdf(rp[j.x], - 1); mdf(rp[j.x] = j.r, 1);}
        for(auto j : qs[i])if(! j.o)rp[j.x] = inf, clr(j.r);
    }
    for(int i = 1; i <= q; ++i)wt(ans[i]), pc('\n');
    return 0;
}

rdCcot

好题啊!套路题,考虑:

对于图长得很奇怪的情况,通常使用钦定“代表元”的方式数连通块。

这道题的连边限制了距离,因此我们考虑深度方面的东西。所以在钦定代表元的时候我们会选择 bfs 序最小的点,因为这样每个连通块就相当于挂在了深度最浅的点上。然后这里又有一个结论:如果其与 bfs 序比其小的点无连边则其为某个连通块的代表元。感性理解可以用反证法一通乱搞,严谨证明可参考题解

所以我们去数区间中不存在往前连边的点数就能得到连通块数。于是我们去记录一个点 u 的连边情况,具体的,去找 u 连出去的 bfs 序更小的点中点编号的前驱后继,如果前驱后继都不在区间中就能贡献到答案。这就是一个二维数点的形式,然后就可以做了。

inline bool cmp(int x, int y){return dfn[x] < dfn[y];}
inline void fdrt(int u, int fa, int tot){
    ma[u] = 0; sz[u] = 1; for(int i = hd[u], v; i; i = e[i].nxt)if((v = e[i].to) != fa and ! vs[v])
        fdrt(v, u, tot), sz[u] += sz[v], Max(ma[u], sz[v]);
    Max(ma[u], tot - sz[u]); if(ma[u] < ma[rt])rt = u;
}
inline void dfss(int u, int fa, int d){
    g.push_back(u); dis[u] = d; sz[u] = 1; for(int i = hd[u], v; i; i = e[i].nxt)
    if((v = e[i].to) != fa and ! vs[v])dfss(v, u, d + 1), sz[u] += sz[v];
}
inline void dfs(int u){
    vi ().swap(g); vs[u] = true; dfss(u, 0, 0); tr.clr();
    sort(g.begin(), g.end(), cmp); for(int i : g)tr.mdf(i, dis[i]);
    for(int i = hd[u], v; i; i = e[i].nxt)if(! vs[v = e[i].to])rt = 0, fdrt(v, u, sz[v]), dfs(rt);
}
inline void init(){
    queue < int > q; q.push(1);
    while(! q.empty()){
        int u = q.front(); q.pop(); dfn[u] = ++tim;
        for(int i = hd[u], v; i; i = e[i].nxt)
            if(! dfn[v = e[i].to])q.push(v);
    }
}

秃子酋长

回滚莫队+链表即可。

inline void add(int x){f[f[x].las].nex = x; f[f[x].nex].las = x;}
inline void del(int x){
    if(x != bg)res -= abs(x - f[x].las); if(x != ed)res -= abs(x - f[x].nex);
    if(x != bg and x != ed)res += abs(f[x].nex - f[x].las);
    if(x == bg)bg = f[x].nex; if(x == ed)ed = f[x].las;
    f[f[x].las].nex = f[x].nex; f[f[x].nex].las = f[x].las;
}

signed main(){
    n = rd(), q = rd();
    for(int i = 1; i <= n; ++i){
        bk[i] = (i - 1) / sn + 1; a[i] = make_pair(rd(), i);
        if(bk[i] != bk[i - 1])rp[bk[i - 1]] = i - 1, lp[bk[i]] = i;
    } rp[bk[n]] = n;
    sort(a + 1, a + 1 + n); bg = a[1].second, ed = a[n].second;
    for(int i = 1; i <= n; ++i)f[a[i].second].las = a[i - 1].second;
    for(int i = 1; i <= n; ++i)f[a[i].second].nex = a[i + 1].second;
    for(int i = 1; i <= n; ++i)if(i != bg)res += abs(i - f[i].las);
    for(int i = 1, l, r; i <= q; ++i)l = rd(), r = rd(), qs[i] = {l, r, i};
    sort(qs + 1, qs + 1 + q);
    for(int i = 1, j = 1, l = 1, r = n; i <= q and j <= bk[n]; ++j)if(bk[qs[i].l] == j){
        while(l < lp[j])del(l++); for(int k = 1; k <= n; ++k)g[k] = f[k];
        ll tmp = res; int hd = bg, tl = ed;
        while(bk[qs[i].l] == j){
            while(r > qs[i].r)del(r--); ll t = res; int Hd = bg, Tl = ed;
            while(l < qs[i].l)del(l++); ans[qs[i].i] = res;
            while(l > lp[j])add(--l); res = t, bg = Hd, ed = Tl; ++i;
        }
        for(int k = 1; k <= n; ++k)f[k] = g[k]; res = tmp; r = n; bg = hd, ed = tl;
    }
    for(int i = 1; i <= q; ++i)wt(ans[i]), pc('\n');
    return 0;
}

rsraogps

这尼玛能有黑?我都不相信我能场切黑的。我们考虑扫描线,然后对于 r 去维护每个 l。考虑把一段相同的区间看成一个块,根据按位与按位或和 gcd 的性质我们可以知道本质上只有 \(\mathcal O(\log)\) 个不同的块,暴力对每个块做区间加就是两只 log,常熟小可以过,实战我就是两只 log 爆标的。当然你可以维护变化量,这样就是每个块 \(\mathcal O(1)\) 修改。

inline bool eq(node x, node y){return x.a == y.a and x.b == y.b and x.c == y.c;}
inline void upd(int x, ui y){for(int i = x; i <= n; i += i & - i)t1[i] += y, t2[i] += (ui)x * y;}
inline void mdf(int l, int r, ui x){upd(l, x); upd(++r, - x);}
inline ui qry(int x){ui s = 0; for(int i = x; i; i ^= i & - i)s += (ui)(x + 1) * t1[i] - t2[i]; return s;}
inline ui ask(int l, int r){return qry(r) - qry(--l);}

const string FileName = "orandgcd";
signed main(){
    // fileio(FileName);
    n = rd(); q = rd();
    for(int i = 1; i <= n; ++i)a[i] = rd();
    for(int i = 1; i <= n; ++i)b[i] = rd();
    for(int i = 1; i <= n; ++i)c[i] = rd();
    for(int i = 1, l, r; i <= q; ++i)
        l = rd(), r = rd(), qs[r].push_back(make_pair(l, i));
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j)lt[j].a &= a[i], lt[j].b |= b[i], lt[j].c = __gcd(lt[j].c, c[i]);
        lt[++m] = {i, i, a[i], b[i], c[i]}; int tot = 0;
        for(int j = 1; j <= m; ++j)
            if(! tot or ! eq(tmp[tot], lt[j]))tmp[++tot] = lt[j];
            else tmp[tot].r = lt[j].r;
        m = tot; for(int j = 1; j <= m; ++j)lt[j] = tmp[j];
        for(int j = 1; j <= m; ++j)mdf(lt[j].l, lt[j].r, lt[j].a * lt[j].b * lt[j].c);
        for(auto j : qs[i])ans[j.second] = ask(j.first, i);
    }
    for(int i = 1; i <= q; ++i)wt(ans[i]), pc('\n');
    return 0;
}

雪に咲く花

加强版。我们如果把序列当作一维,时间当作一维,之前的做法我们可看作是在序列上做文章,把一段进行修改,现在我们换维考虑,思考对于一个位置再什么时候会发生变化?因为之前提过那三个东西的变化都是 \(\mathcal O(\log)\) 级别的,并且这些 log 都往往跑不满,于是我们非常暴力的倒序修改,多维护一个修改的时间即可。

signed main(){
    n = rd(), q = rd();
    for(ui i = 1; i <= n; ++i)a[i] = rd();
    for(ui i = 1; i <= n; ++i)b[i] = rd();
    for(ui i = 1; i <= n; ++i)c[i] = rd();
    for(ui i = 1; i <= q; ++i)ql[i] = rd(), ++qc[qr[i] = rd()];
    for(ui i = 1; i <= n; ++i)qc[i] += qc[i - 1], cnt[i] = qc[i];
    for(ui i = 1; i <= q; ++i)id[cnt[qr[i]]--] = i;
    for(ui i = 1, j = 1; i <= n; ++i){
        ui p = i; for(; --p; ){
            ui x = a[p] & a[p + 1], y = b[p] | b[p + 1], z = __gcd(c[p], c[p + 1]);
            if(x == a[p] and y == b[p] and z == c[p])break;
            a[p] = x; b[p] = y; c[p] = z;
        }
        res[i] = res[i - 1] + val[i - 1] * (i - 1 - tim[i - 1]);
        for(; ++p <= i; ){
            res[p] += val[p] * (i - 1 - tim[p]);
            tim[p] = i - 1; val[p] = val[p - 1] + a[p] * b[p] * c[p];
        }
        ui tmp = res[i] + val[i] * (i - tim[i]);
        for(; j <= qc[i]; ++j)
            ans[id[j]] = tmp - res[ql[id[j]] - 1] - val[ql[id[j]] - 1] * (i - tim[ql[id[j]] - 1]);
    }
    for(ui i = 1; i <= q; ++i)wt(ans[i]), pc('\n');
    return 0;
}

天天爱跑步

对于每条路径考虑怎么去贡献答案?肯定是包含在路径上的点可能贡献答案,然后我们可以列出贡献的方式:

\[dep_i+w_i=dep_s \]

\[dep_i+(dep_s+dep_t-2\times dep_{lca}-w_i)=dep_t \]

然后就在 dfs 的过程中维护一个桶即可。然后因为是路径,所以还需要树上差分一下贡献。

inline void dfs(int u, int f){
    int t0 = b[0][dep[u] + w[u]], t1 = b[1][dep[u] - w[u]];
    for(auto i : qs[0][u])b[0][i.second] += i.first;
    for(auto i : qs[1][u])b[1][i.second] += i.first;
    for(int i = hd[u], v; i; i = e[i].nxt)if((v = e[i].to) != f)dfs(v, u);
    ans[u] = b[0][dep[u] + w[u]] + b[1][dep[u] - w[u]] - t0 - t1;
}

signed main(){
    n = rd(), m = rd(); for(int i = 1, u, v; i < n; ++i)
        u = rd(), v = rd(), addedge(u, v);
    for(int i = 1; i <= n; ++i)w[i] = rd();
    dfs1(1, 0); dfs2(1, 1);
    for(int i = 1, s, t; i <= m; ++i){
        s = rd(), t = rd(); int lca = getlca(s, t);
        qs[0][s].push_back(make_pair(1, dep[s]));
        qs[0][fa[lca]].push_back(make_pair(- 1, dep[s]));
        qs[1][t].push_back(make_pair(1, 2 * dep[lca] - dep[s]));
        qs[1][lca].push_back(make_pair(- 1, 2 * dep[lca] - dep[s]));
    }
    dfs(1, 0); for(int i = 1; i <= n; ++i)wt(ans[i]), pc(' ');
    return 0;
}

rldcot

考虑支配对,对于每个点,我们假设其作为 lca,去找可能做贡献的支配对,这个可以通过 dsu on tree 做到一只 log,然后就变成二维数点即可。

inline void dsu(int u, int f, bool o){
    for(int i = hd[u], v; i; i = e[i].nxt)
        if((v = e[i].to) != f and v != son[u])dsu(v, u, false);
    if(son[u])dsu(son[u], u, true); ms[u].push_back(make_pair(u, col[u]));
    for(int i = hd[u], v; i; i = e[i].nxt)if((v = e[i].to) != f and v != son[u]){
        for(int j = lp[v]; j <= rp[v]; ++j){
            int x = rk[j]; auto p = ss.lower_bound(x);
            if(p != ss.end())ms[x].push_back(make_pair(*p, col[u]));
            if(p != ss.begin())ms[*(--p)].push_back(make_pair(x, col[u]));
        }
        for(int j = lp[v]; j <= rp[v]; ++j)ss.insert(rk[j]);
    }
    ss.insert(u); if(! o)ss.clear();
}

signed main(){
    n = rd(), q = rd(); for(int i = 1, u, v, w; i < n; ++i)
        u = rd(), v = rd(), w = rd(), addedge(u, v, w);
    for(int i = 1; i <= q; ++i)qs[rd()].push_back(make_pair(rd(), i));
    dfs(1, 0); for(int i = 1; i <= n; ++i)lsh[i] = dis[i];
    sort(lsh + 1, lsh + 1 + n); m = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
    for(int i = 1; i <= n; ++i)col[i] = lower_bound(lsh + 1, lsh + 1 + m, dis[i]) - lsh;
    dsu(1, 0, 1); for(int i = 1; i <= m; ++i)ok[i] = n + 1;
    for(int i = n; i; --i){
        for(auto j : ms[i])if(j.first < ok[j.second])
            mdf(j.first, ok[j.second], 1), ok[j.second] = j.first;
        for(auto j : qs[i])ans[j.second] = qry(j.first);
    }
    for(int i = 1; i <= q; ++i)wt(ans[i]), pc('\n');
    return 0;
}
posted @ 2025-09-08 19:49  Lyrella  阅读(44)  评论(3)    收藏  举报