[LOJ2983] [WC2019] 数树

本文深入解析了一道复杂的数数题算法,通过详尽的数学推导和代码实现,展示了如何利用容斥原理、二项式定理和多项式运算解决特定的计数问题。文章覆盖了从基础的子任务到高级的子任务2,提供了完整的解决方案。

题目链接

LOJ:https://loj.ac/problem/2983

BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=5475

洛谷:https://www.luogu.org/problemnew/show/P5206

Soltion

超级毒瘤数数题...窝看了一晚上才看懂...

%%%rqy


subtask 0

很容易可以看出每个公共的边连成的联通块被绑一起了,所以答案就是\(y^{p}\),其中\(p\)为联通块个数。

也就是说答案为\(y^{n-m}\),其中\(m\)为公共边条数。


Subtask 1

\(f(s)=y^{n-|s|}\),其中\(s\)为边集。

那么我们枚举树的形态可以得到答案为:
\[ ans=\sum_{E_2}f(E_1\cap E_2) \]
其中\(E_2\)枚举的是边集,\(E_1\)是题目给出的边集。这个\(E_1\cap E_2\)不是很好办,我们可以利用下面这个容斥的式子化简:
\[ f(s)=\sum_{t\subseteq s}\sum_{p \subseteq t}(-1)^{|t|-|p|}f(p) \]
下面会给出证明,当然,读者自证不难。带进去可得:
\[ \begin{align} ans&=\sum_{E_2}\sum_{t\subseteq E_1,t\subseteq E_2}\sum_{p\subseteq t}(-1)^{|t|-|p|}f(p)\\ &=\sum_{t\subseteq E_1} g(t)\sum_{p\subseteq t}(-1)^{|t|-|p|}f(p)\\ &=\sum_{t\subseteq E_1} g(t)\sum_{p\subseteq t}(-1)^{|t|-|p|}y^{n-|p|}\\ &=\sum_{t\subseteq E_1} g(t)y^{n-|t|}\sum_{p\subseteq t}(-1)^{|t|-|p|}y^{|t|-|p|}\\ &=\sum_{t\subseteq E_1} g(t)y^{n-|t|}\sum_{p\subseteq t}(-y)^{|t|-|p|}\\ &=\sum_{t\subseteq E_1} g(t)y^{n-|t|}(1-y)^{|t|}\\ \end{align} \]
其中倒数第二个等式到最后一个等式用到了二项式定理,\(g(s)\)表示包含边集\(s\)的树的个数,可以证明:
\[ g(s)=n^{k-2}\prod_{i=1}^ka_i \]
证明下面会给出,其中,\(s\)被分成了\(k\)个联通块,大小分别为\(a_1,a_2\cdots a_k\)

这套交换求和符号还是看得懂的吧...不然怎么敢来刚这个题

那么带进去可得:
\[ \begin{align} ans&=\sum_{t\subseteq E_1}\left(n^{k-2}\prod_{i=1}^ka_i\right)y^{k}(1-y)^{n-k}\\ &=\frac{(1-y)^n}{n^2}\sum_{t\subseteq E_1}\prod_{i=1}^{k}\frac{ny}{1-y}\cdot a_i \end{align} \]
至此,这玩意还是指数级的,但是我们可以发现,\(\frac{ny}{1-y}\)是固定的,设其为\(k\),也就是说,每个联通块有\(ka_i\)的贡献。

那么,其实我们就可以\(dp\)了,设\(f_{i,j}\)表示第\(i\)个点,只考虑子树\(i\)所在的联通块\(size\)\(j\)的贡献,注意不考虑\(i\)这个联通块的贡献(这里贡献指的是若干个\(ka_i\)的乘积)。

然后直接暴力背包转移,时间复杂度降为了\(O(n^2)\)

我们固定\(f_{i,j}\)\(i\),设一个幂级数:
\[ F_i(x)=\sum_{j=1}^nf_{i,j}x^j \]
再设答案为\(g_i(x)\)
\[ g_i(x)=\sum_{j=1}^{n}kj\cdot f_{i,j}=kF_i'(1) \]
其中\(F_i'\)表示求导。

那么背包转移就可以写成:
\[ F_i(x)=x\prod_{j\in son_i}(g_j+F_j(x)) \]
那么可得:
\[ F_i'(x)=\prod_{j\in son_i}(g_j+F_j(x))+x\left(\prod_{j\in son_i}(g_j+F_j(x))\right)\left(\sum_{j\in son_i}\frac{F_j'(x)}{g_j+F_j(x)}\right) \]
这里是照抄的乘积求导法则:
\[ \left(\prod_{i=1}^kF_i(x)\right)'=\sum_{i=1}^{n}F_i'(x)\prod_{j\ne i}^kF_j(x) \]
换一下求和符号就是:
\[ \left(\prod_{i=1}^kF_i(x)\right)'=\prod_{i=1}^{k}F_i(x)\sum_{j=1}^{k}\frac{F_j'(x)}{F_j(x)} \]
然后令\(x=1\)得到:
\[ F_i'(1)=\prod_{j\in son_i}(g_j+F_j(1))+\left(\prod_{j\in son_i}(g_j+F_j(1))\right)\left(\sum_{j\in son_i}\frac{F_j'(1)}{g_j+F_j(1)}\right) \]
\(t_i=F_i(1)=\prod_{j\in son_i}(g_j+F_j(x))\),那么把上面的式子抄下来:
\[ g_i=F_i'(1)=t_i(1+\sum_{j\in son_i}\frac{g_j}{g_j+t_j}) \]

\[ t_i=\prod_{j\in son}g_j+t_j \]

然后\(O(n)\)递推就好了。


Subtask 2

我们照抄上面的式子:
\[ ans=\sum_{s}y^{n-|s|}(1-y)^{|s|}g^2(s) \]
这里\(s\)枚举的是公共部分,所以\(g(s)\)要平方,因为两棵树都要满足。

展开:
\[ ans=\frac{(1-y)^n}{n^4}\sum_{s}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)a_i^2 \]
过程和上面一样。

我们换种方式枚举,枚举多少个联通块以及大小分别是多少,显然联通块无顺序所以要除以\(k!\),然后点有标号所以乘上组合数\(\frac{n!}{\prod a_i!}\)\(a\)个点的树个数为\(a^{a-2}\),写出来就是:
\[ \begin{align} ans&=\frac{(1-y)^n}{n^4}\sum_{k=1}^n\frac{1}{k!}\sum_{a_1+a_2\cdots a_k=n}\frac{n!}{\prod a_i!}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)a_i^2\cdot a_i^{a_i-2}\\ &=\frac{(1-y)^nn!}{n^4}\sum_{k=1}^n\frac{1}{k!}\sum_{a_1+a_2\cdots a_k=n}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)\frac{a_i^{a_i}}{a_i!}\\ \end{align} \]
后面的卷积形式写成多项式就是:
\[ \begin{align} ans&=\frac{(1-y)^nn!}{n^4}[x^n]\sum_{k=1}^n\frac{1}{k!}\left(\sum_{a=1}^{+\infty}\frac{n^2y}{1-y}\cdot \frac{a^a}{a!}x^a\right)^k\\ \end{align} \]
注意到这是个多项式\(\exp\)形式,直接算就好了,复杂度\(O(n\log n)\)

其实多项式\(\exp\)的组合意义就是带标号的多重背包,所以上面符合多项式\(\exp\)的式子也就不出意外了。


上面lemma的proof

lemma 1
\[ f(s)=\sum_{t\subseteq s}\sum_{p \subseteq t}(-1)^{|t|-|p|}f(p) \]
其中\(f\)是一个和\(|s|\)有关的函数。

那么交换求和符号:
\[ f(s)=\sum_{p \subseteq s}f(p)\sum_{p\subseteq t,t\subseteq s}(-1)^{|t|-|p|} \]

\[ f(s)=\sum_{p \subseteq s}f(p)\sum_{t\subseteq s-p}(-1)^{|t|} \]

\[ f(s)=\sum_{p \subseteq s}f(p)[s=p] \]

可以发现等式左边右边都是\(f(s)\),证毕。


lemma 2:

我们要把\(k\)个联通块组成的森林连成一棵树的方案数,其中联通块大小分别为\(a_1,a_2\cdots a_k\)

考虑每个联通块视为一个点,那么它的\(prufer\)序列共有\(k-2\)个点,每个点在\([1,k]\)

我们枚举每个点是什么,统计方案数:
\[ \sum_{1\leqslant b_1,b_2\cdots b_{k-2}\leqslant k}\prod_{i=1}^{k}a_i^{c_i+1} \]
其中\(b_i\)为第\(i\)位的数,\(c_i\)表示\(i\)出现了多少次。

交换求和符号:
\[ \begin{align} &\left(\prod_{i=1}^ka_i\right)\sum_{1\leqslant b_1,b_2\cdots b_{k-2}\leqslant k}\prod_{i=1}^{k}a_i^{c_i}\\ =&\left(\prod_{i=1}^ka_i\right)\prod_{i=1}^{k-2}\sum_{x=1}^{k}a_{x}\\ =&\left(\prod_{i=1}^ka_i\right)\prod_{i=1}^{k-2}n\\ =&n^{n-2}\prod_{i=1}^ka_i\\ \end{align} \]


注意当\(y=1\)时上面很多式子都没有意义,需要特判。

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

void read(int &x) {
    x=0;int f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}

#define lf double
#define ll long long 
#define mp make_pair
#define fr first
#define sc second

const int maxn = 6e5+10;
const int inf = 1e9;
const lf eps = 1e-8;
const int mod = 998244353;

int add(int x,int y) {return x+y>mod?x+y-mod:x+y;}
int del(int x,int y) {return x-y<0?x-y+mod:x-y;}
int mul(int x,int y) {return 1ll*x*y-1ll*x*y/mod*mod;}

int n,y;

int qpow(int a,int x) {
    int res=1;
    for(;x;x>>=1,a=mul(a,a)) if(x&1) res=mul(res,a);
    return res;
}

namespace fuckpps {
    void solve(int op) {
        if(op==0) write(1);
        else if(op==1) write(qpow(n,n-2));
        else write(qpow(n,2*n-4));
    }
}

namespace subtask0 {
    void solve() {
        map<pair<int,int >,int > s;int ans=0;
        for(int i=1,x,yy;i<n;i++) read(x),read(yy),s[mp(min(x,yy),max(x,yy))]=1;
        for(int i=1,x,yy;i<n;i++) read(x),read(yy),ans+=s[mp(min(x,yy),max(x,yy))];
        write(qpow(y,n-ans));
    }
}

namespace subtask1 {
    int head[maxn],tot,g[maxn],t[maxn],k;
    struct edge{int to,nxt;}e[maxn<<1];

    void ad(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
    void ins(int u,int v) {ad(u,v),ad(v,u);}
    
    void dfs(int x,int fa) {
        g[x]=k,t[x]=1;
        for(int i=head[x],v;i;i=e[i].nxt)
            if((v=e[i].to)!=fa) {
                dfs(v,x);
                g[x]=add(g[x],mul(g[v],qpow(add(g[v],t[v]),mod-2)));
                t[x]=mul(t[x],add(g[v],t[v]));
            }
        g[x]=mul(g[x],t[x]);
    }
    
    void solve() {
        k=mul(mul(n,y),qpow(1-y+mod,mod-2));
        for(int i=1,u,v;i<n;i++) read(u),read(v),ins(u,v);
        dfs(1,0);write(mul(g[1],mul(qpow(1-y+mod,n),qpow(mul(n,n),mod-2))));
    }
}

namespace subtask2 {
    int w[maxn],rw[maxn],N,bit,pos[maxn],f[maxn],fac[maxn],ifac[maxn],inv[maxn],mxn,g[maxn],K;
    int tmp[7][maxn];
    
    void ntt_init() {
        w[0]=rw[0]=1,w[1]=qpow(3,(mod-1)/N);mxn=N;
        for(int i=2;i<=N;i++) w[i]=mul(w[i-1],w[1]);
        rw[1]=qpow(w[1],mod-2);
        for(int i=2;i<=N;i++) rw[i]=mul(rw[i-1],rw[1]);
    }
    
    void ntt(int *r,int op) {
        for(int i=1;i<N;i++) if(pos[i]>i) swap(r[i],r[pos[i]]);
        for(int i=1,d=mxn>>1;i<N;i<<=1,d>>=1)
            for(int j=0;j<N;j+=i<<1)
                for(int k=0;k<i;k++) {
                    int x=r[j+k],y=mul((op==1?w:rw)[k*d],r[i+j+k]);
                    r[j+k]=add(x,y),r[i+j+k]=del(x,y);
                }
        if(op==-1) {int d=qpow(N,mod-2);for(int i=0;i<N;i++) r[i]=mul(r[i],d);}
    }
    
    void ntt_get(int len) {
        for(N=1,bit=0;N<=len;N<<=1,bit++) ;
        for(int i=0;i<N;i++) pos[i]=pos[i>>1]>>1|((i&1)<<(bit-1));
    }
    
    void poly_inv(int *r,int *t,int len) {
        if(len==1) return t[0]=qpow(r[0],mod-2),void();
        poly_inv(r,t,len>>1);
        for(int i=0;i<len>>1;i++) tmp[0][i]=t[i],tmp[1][i]=r[i];
        for(int i=len>>1;i<len;i++) tmp[0][i]=0,tmp[1][i]=r[i];
        ntt_get(len),ntt(tmp[0],1),ntt(tmp[1],1);
        for(int i=0;i<N;i++) t[i]=del(mul(2,tmp[0][i]),mul(mul(tmp[1][i],tmp[0][i]),tmp[0][i]));
        ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
        for(int i=0;i<len<<1;i++) tmp[0][i]=tmp[1][i]=0;
    }
    
    void poly_der(int *r,int *t,int len) {
        ntt_get(len);
        for(int i=1;i<len;i++) t[i-1]=mul(i,r[i]);
        for(int i=len-1;i<N;i++) t[i]=0;
    }
    
    void poly_int(int *r,int *t,int len) {
        ntt_get(len);
        for(int i=0;i<len;i++) t[i+1]=mul(inv[i+1],r[i]);t[0]=0;
        for(int i=len+1;i<N;i++) t[i]=0;
    }
    
    void poly_ln(int *r,int *t,int len) {
        poly_der(r,tmp[2],len);
        poly_inv(r,tmp[3],len);
        ntt_get(len),ntt(tmp[2],1),ntt(tmp[3],1);
        for(int i=0;i<N;i++) tmp[3][i]=mul(tmp[2][i],tmp[3][i]);
        ntt(tmp[3],-1);
        poly_int(tmp[3],t,len);
        for(int i=0;i<len<<1;i++) tmp[3][i]=0;
    }
    
    void poly_exp(int *r,int *t,int len) {
        if(len==1) return t[0]=1,void();
        poly_exp(r,t,len>>1);
        for(int i=0;i<len>>1;i++) tmp[4][i]=r[i],tmp[5][i]=t[i];
        for(int i=len>>1;i<len;i++) tmp[4][i]=r[i],tmp[5][i]=0;
        poly_ln(tmp[5],tmp[6],len);
        for(int i=0;i<len;i++) tmp[4][i]=del(tmp[4][i],tmp[6][i]);
        tmp[4][0]=add(tmp[4][0],1);
        ntt_get(len),ntt(tmp[4],1),ntt(tmp[5],1);
        for(int i=0;i<N;i++) t[i]=mul(tmp[4][i],tmp[5][i]);
        ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
    }
    
    void solve() {
        for(N=1,bit=0;N<=n<<2;N<<=1,bit++);ntt_init();
        inv[0]=fac[0]=ifac[0]=inv[1]=1;K=mul(mul(n,mul(n,y)),qpow(1-y+mod,mod-2));
        for(int i=2;i<N;i++) inv[i]=mul(mod-mod/i,inv[mod%i]);
        for(int i=1;i<N;i++) fac[i]=mul(fac[i-1],i);
        for(int i=1;i<N;i++) ifac[i]=mul(ifac[i-1],inv[i]);
        for(int i=1;i<=n;i++) f[i]=mul(K,mul(qpow(i,i),ifac[i]));
        ntt_get(n);poly_exp(f,g,N);
        write(mul(g[n],mul(mul(qpow(1-y+mod,n),fac[n]),qpow(qpow(n,4),mod-2))));
    }
}

int main() {
    read(n),read(y);int op;read(op);
    if(y==1) fuckpps :: solve(op);
    else if(op==0) subtask0 :: solve();
    else if(op==1) subtask1 :: solve();
    else subtask2 :: solve();
    return 0;
}

转载于:https://www.cnblogs.com/hbyer/p/10677674.html

06-21
<think>我们正在查询与LOJ6279相关的编程题目或解决方案。LOJ(LibreOJ)是一个在线的评测系统,主要收录算法竞赛题目。根据题号6279,我们需要确定该题目的具体内容。由于我无法直接访问网络,我将基于已知信息进行推理。在算法竞赛中,LOJ的题目编号通常与特定的题目对应。题号6279可能对应一个特定的问题。根据常见的题目类型,我们可以推测它可能涉及据结构或算法。然而,为了准确回答,我们可以尝试回忆或推断常见的LOJ题目。例如,LOJ6000系列题目多为据结构题,如线段组等。实际上,通过搜索记忆,我回想起LOJ6279是“列分块入门3”(BlockArray3)题目。这是“分块”系列题目中的一题。题目描述:给出一个长为n的列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(即最大的小于x的)。因此,用户需要的是关于LOJ6279题目的描述和解决方案。解决方案:由于题目要求分块处理,我们将列分成若干块,每块大小为sqrt(n)。对于每个块,我们维护一个有序的组(用于二分查找)以及一个加法标记(记录整个块被加了多少)。操作:1.区间加法:对于整块,直接更新加法标记;对于不完整的块,暴力更新元素值,然后重新排序该块(或者重新构建有序组)。2.查询区间内小于x的前驱:遍历区间内的所有块。对于整块,在块的有序组中二分查找小于(x-加法标记)的最大值;对于不完整的块,暴力遍历每个元素,判断是否小于x(注意加上加法标记)并更新最大值。代码实现(C++)示例:```cpp#include<iostream>#include<vector>#include<cmath>#include<algorithm>#include<climits>usingnamespacestd;constintMAXN=100010;constintBLOCK_SIZE=320;//约sqrt(MAXN)intn;vector<int>a;vector<int>block_add;//每块的加法标记vector<vector<int>>sorted_blocks;//每块的有序组voidinit(){intblock_num=(n+BLOCK_SIZE-1)/BLOCK_SIZE;block_add.assign(block_num,0);sorted_blocks.resize(block_num);for(inti=0;i<n;i++){sorted_blocks[i/BLOCK_SIZE].push_back(a[i]);}for(inti=0;i<block_num;i++){sort(sorted_blocks[i].begin(),sorted_blocks[i].end());}}voidupdate_block(intblock_id){sorted_blocks[block_id].clear();intstart=block_id*BLOCK_SIZE;intend=min((block_id+1)*BLOCK_SIZE,n);for(inti=start;i<end;i++){sorted_blocks[block_id].push_back(a[i]);}sort(sorted_blocks[block_id].begin(),sorted_blocks[block_id].end());}voidadd(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;if(block_l==block_r){for(inti=l;i<=r;i++){a[i]+=c;}update_block(block_l);}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){a[i]+=c;}update_block(block_l);for(inti=block_l+1;i<block_r;i++){block_add[i]+=c;}for(inti=block_r*BLOCK_SIZE;i<=r;i++){a[i]+=c;}update_block(block_r);}}intquery(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;intans=-1;if(block_l==block_r){for(inti=l;i<=r;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}for(inti=block_l+1;i<block_r;i++){//在块i的有序组中二分查找小于c-block_add[i]的最大值inttarget=c-block_add[i];autoit=lower_bound(sorted_blocks[i].begin(),sorted_blocks[i].end(),target);if(it!=sorted_blocks[i].begin()){it--;intval=*it+block_add[i];if(val>ans)ans=val;}}for(inti=block_r*BLOCK_SIZE;i<=r;i++){intval=a[i]+block_add[block_r];if(val<c){if(val>ans)ans=val;}}}returnans;}intmain(){cin>>n;a.resize(n);for(inti=0;i<n;i++){cin>>a[i];}init();for(inti=0;i<n;i++){intop,l,r,c;cin>>op>>l>>r>>c;l--;r--;//如果题目输入是1-indexed,则转换为0-indexedif(op==0){add(l,r,c);}else{cout<<query(l,r,c)<<endl;}}return0;}```注意:上述代码是示例代码,可能需要根据题目具体输入要求进行调整。§§相关问题§§1.LOJ6279题目的具体描述是什么?2.分块算法的时间复杂度是多少?3.除了分块,还有哪些方法可以解决LOJ6279?4.如何优化分块算法的常?5.分块算法适用于哪些场景?</think>### LOJ 6279 题目分析与解决方案 **题目描述**:LOJ 6279 是 LibreOJ 上的 **"列分块入门 3"**,属于分块算法的经典练习题。核心要求如下: - **操作 1**:区间 $[l, r]$ 内每个加上值 $c$ - **操作 2**:查询区间 $[l, r]$ 内小于 $c$ 的**最大元素值**(即前驱),不存在时返回 $-1$ #### 分块算法设计 分块的核心思想是将列划分为 $\sqrt{n}$ 大小的块,平衡暴力操作与批量处理。 ##### 关键据结构 1. **块内有序组**: - 每块维护一个排序后的组,用于二分查找前驱。 - 示例:块 $B_i$ 的排序组为 $S_i$。 2. **加法懒标记**: - 记录整块被加的总值 $add_i$(不影响块内有序性)。 ##### 操作实现 1. **区间加法($[l, r] + c$)**: - **整块**:直接更新懒标记 $add_i \gets add_i + c$,时间复杂度 $O(1)$。 - **碎块**:暴力修改元素值,并**重构有序组**(排序),时间复杂度 $O(\sqrt{n} \log \sqrt{n})$。 2. **查询前驱($[l, r]$ 内 $< c$ 的最大值)**: - **整块**:在有序组 $S_i$ 中二分查找 $c - add_i$ 的前驱,时间复杂度 $O(\log \sqrt{n})$。 - **碎块**:暴力遍历元素,检查 $a_j + add_i < c$ 并更新最大值,时间复杂度 $O(\sqrt{n})$。 #### 代码实现(C++) ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> #include <climits> using namespace std; const int MAXN = 100010; int n, block_size; vector<int> a; vector<int> add_tag; // 块的加法标记 vector<vector<int>> blocks; // 每块的有序组 void init() { block_size = sqrt(n); int block_num = (n + block_size - 1) / block_size; add_tag.assign(block_num, 0); blocks.resize(block_num); for (int i = 0; i < n; i++) blocks[i / block_size].push_back(a[i]); for (int i = 0; i < block_num; i++) sort(blocks[i].begin(), blocks[i].end()); } void rebuild_block(int block_id) { blocks[block_id].clear(); int start = block_id * block_size; int end = min(start + block_size, n); for (int i = start; i < end; i++) blocks[block_id].push_back(a[i]); sort(blocks[block_id].begin(), blocks[block_id].end()); } void range_add(int l, int r, int c) { int block_l = l / block_size, block_r = r / block_size; if (block_l == block_r) { // 同一块内 for (int i = l; i <= r; i++) a[i] += c; rebuild_block(block_l); } else { // 左侧碎块 for (int i = l; i < (block_l + 1) * block_size; i++) a[i] += c; rebuild_block(block_l); // 中间整块 for (int i = block_l + 1; i < block_r; i++) add_tag[i] += c; // 右侧碎块 for (int i = block_r * block_size; i <= r; i++) a[i] += c; rebuild_block(block_r); } } int query_predecessor(int l, int r, int c) { int ans = -1; int block_l = l / block_size, block_r = r / block_size; // 左侧碎块暴力查询 for (int i = l; i < min(r + 1, (block_l + 1) * block_size); i++) { int val = a[i] + add_tag[block_l]; if (val < c && val > ans) ans = val; } // 中间整块二分查找 for (int i = block_l + 1; i < block_r; i++) { int target = c - add_tag[i]; auto it = lower_bound(blocks[i].begin(), blocks[i].end(), target); if (it != blocks[i].begin()) { it--; int val = *it + add_tag[i]; if (val < c && val > ans) ans = val; } } // 右侧碎块暴力查询 if (block_l != block_r) { for (int i = block_r * block_size; i <= r; i++) { int val = a[i] + add_tag[block_r]; if (val < c && val > ans) ans = val; } } return ans; } int main() { cin >> n; a.resize(n); for (int i = 0; i < n; i++) cin >> a[i]; init(); for (int i = 0; i < n; i++) { int op, l, r, c; cin >> op >> l >> r >> c; l--; r--; // 转为0-indexed if (op == 0) range_add(l, r, c); else cout << query_predecessor(l, r, c) << endl; } return 0; } ``` #### 算法分析 - **时间复杂度**: - 单次修改/查询:$O(\sqrt{n} \log \sqrt{n})$(碎块排序主导)。 - 总操作 $m$ 次:$O(m \sqrt{n} \log n)$。 - **空间复杂度**:$O(n)$。 #### 优化技巧 1. **减少排序次**: - 碎块修改时只重构受影响块的有序组。 2. **块大小调整**: - 实测调整块大小为 $n^{0.6}$ 可能更快(需测试)。 #### 应用场景 分块算法适用于**强制在线**的区间问题(如 LOJ列分块系列题),在 $O(\sqrt{n})$ 复杂度下平衡修改与查询[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值