LGP12461 [Ynoi 2018-E] 星野爱 学习笔记

LGP12461 [Ynoi 2018-E] 星野爱 学习笔记

Luogu Link

前言

第一次体会到调块长的快乐。

本题解借鉴:这篇

题意简述

给定一张无向图 \(G\)。设 \(R[i]\)\(i\) 的所有邻接点构成的可重集合。可能有重边,但没有自环。

每个结点有一个权值 \(w_i\),初始时都为 \(0\)。支持以下两种操作:

  • 1 l r x:对于 \(i\in[l,r]\)\(j\in R[i]\),令 \(w_j\) 增加 \(x\)。(这个过程中一个点可以被加多次)
  • 2 l r:计算 \(\sum_{i=l}^r\sum_{j\in R[i]}w_j\)。(同理,一个点也有可能被算多次)

输出的值对 \(2^{64}\) 取模。

\(1\le n,m,q\le 2\times 10^5\)\(0\le x\le 10^9\)

做法解析

其实这道题和图论没有任何关系。你考虑把所有 \(R[i]\) 展开写出来,然后把 \(R[1],R[2],\dots,R[n]\) 拼在一起形成一个长 \(2m\) 的序列 \(A\)。这样原问题就等价于从此序列上选一个区间修改或求和。

诶这不是烂大街问题吗……非也。仔细看看,你会发现这个修改和查询长得非常清奇:我在序列某端修改,可能会影响到另一端某个询问求和,因为我们不是在修改 \(A\) 序列本身,\(A\) 序列更像是一个“引用序列”,我们在以 \(a_i\) 作为下标去改 \(w\) 值。这要怎么做呢?

看着有点麻烦?麻烦的东西考虑分块。将 \(A\) 按照 \(\sqrt{2m}\) 分块。幸运的,我们发现这题维护的信息(增加、求和)非常可差分,所以我们思考把询问和修改全部离线下来之后,整块散块间的影响。

散块修改对散块查询暴力就行了。整块修改怎么办呢?我们枚举每个块,对于每个块都扫一遍询问序列,记 \(d\) 为当前这个块的每个元素获得的增量。修改操作就是把 \(d\) 加上 \(x\),查询就看询问区间和自己的交集长度 \(len\),这个块就会给这个询问提供 \(len\times d\) 的贡献。

散块修改对整块查询呢?在上文枚举每个块的过程中,我们查看每次对散块的修改与当前整块的交的大小,以此计算产生的贡献并加到另一个标记上,查询的时候直接加这个标记就行。

总时间复杂度:\(O(n\sqrt{n})\)(我们认为 \(n,m\) 同阶)。

不过有一点值得说的:你感性理解一下,对于这道题块分的越多我们会越麻烦。我在设块长等于 \(\sqrt{2m}\) 的时候代码会随机TLE \([0,3]\) 个点。所以不妨把块长调到 \(4\sqrt{2m}\),此时评测记录显示用时会从原来的 \(\text{4.00s}\) 左右跑到 \(\text{2.10s}\) 左右,快了一倍。顺便我还拿了个最优解。这就是调块长给我带来的自信。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=2e5+5,Knum=2e5+5;
int N,M,Q,X,Y;
vector<int> Gr[MaxN];
void addudge(int u,int v){
    Gr[u].push_back(v);
    Gr[v].push_back(u);
}
int nln,P[MaxN<<1],fst[MaxN],lst[MaxN];ulolo A[MaxN];
int ksiz,knum,lb[Knum],rb[Knum],bel[MaxN<<1];
struct quer{int t,l,r,x;}Qu[MaxN];
int cnt[MaxN],psum[MaxN<<1];
ulolo ans[MaxN];
int main(){
    readis(N,M,Q);
    for(int i=1;i<=M;i++)readis(X,Y),addudge(X,Y);
    for(int i=1;i<=N;i++){
        fst[i]=lst[i-1]+1;
        for(int v : Gr[i])P[++nln]=v;
        lst[i]=nln;
    }
    ksiz=min(((int)sqrt(nln))<<2,nln),knum=pcedi(nln,ksiz);
    for(int i=1;i<=knum;i++)lb[i]=rb[i-1]+1,rb[i]=rb[i-1]+ksiz;
    rb[knum]=nln;for(int i=1;i<=nln;i++)bel[i]=pcedi(i,ksiz);
    for(int i=1;i<=Q;i++){
        auto &[ct,cl,cr,cx]=Qu[i];readis(ct,X,Y);
        if(ct==1)readi(cx);cl=fst[X],cr=lst[Y];if(cl>cr)continue;
        auto sadd=[&](int l,int r,int x)->void {for(int i=l;i<=r;i++)A[P[i]]+=x;};
        auto squery=[&](int l,int r)->ulolo {ulolo res=0;for(int i=l;i<=r;i++)res+=A[P[i]];return res;};
        if(bel[cl]==bel[cr]){
            if(ct==1)sadd(cl,cr,cx);
            if(ct==2)ans[i]=squery(cl,cr);
            continue;
        }
        if(ct==1)sadd(cl,rb[bel[cl]],cx),sadd(lb[bel[cr]],cr,cx);
        if(ct==2)ans[i]=squery(cl,rb[bel[cl]])+squery(lb[bel[cr]],cr);
    }
    ulolo tag1,tag2;
    for(int k=1;k<=knum;k++){
        for(int i=lb[k];i<=rb[k];i++)cnt[P[i]]++;
        for(int i=1;i<=nln;i++)psum[i]=psum[i-1]+cnt[P[i]];
        tag1=tag2=0;
        auto radd=[&](int l,int r,int x)->void {tag1+=1ll*x*(psum[r]-psum[l-1]);};
        auto badd=[&](int x)->void {tag2+=x;};
        auto rquery=[&](int l,int r)->ulolo {return tag2*(psum[r]-psum[l-1]);};
        auto bquery=[&]()->ulolo {return tag1;};
        for(int i=1;i<=Q;i++){
            auto &[ct,cl,cr,cx]=Qu[i];if(cl>cr)continue;
            if(ct==1){
                if(bel[cl]==bel[cr])radd(cl,cr,cx);
                else{
                    radd(cl,rb[bel[cl]],cx),radd(lb[bel[cr]],cr,cx);
                    if(cl<lb[k]&&rb[k]<cr)badd(cx);
                }
            }
            if(ct==2){
                ans[i]+=rquery(cl,cr);
                if(cl<lb[k]&&rb[k]<cr)ans[i]+=bquery();
            }
        }
        for(int i=lb[k];i<=rb[k];i++)cnt[P[i]]--;
    }
    for(int i=1;i<=Q;i++)if(Qu[i].t==2)writil(ans[i]);
    return 0;
}
posted @ 2025-05-14 16:32  矞龙OrinLoong  阅读(12)  评论(0)    收藏  举报