zoukankan      html  css  js  c++  java
  • 大厨的分块题

    ( ext{Chef and Churu})

    解法

    拼接两个算法分块

    这道题显然有两种信息维护方式:

    • 维护 (A) 的前缀和,对于单个函数 (f(i)),直接查询对应的 (A)
    • 对于 (A_i) 的修改,修改区间包含 (i) 的函数 (f(i))

    第二种维护可以将函数分块,维护每个块内所有 (A_i) 的贡献次数,差分实现可以做到 (mathcal O(ncdot frac{n}{B}))(具体实现请见 getNum() 函数)预处理。但无法处理散块的情况,而散块满足大小在 (B) 范围,所以可以暴力更新函数。

    如何维护 (A) 的前缀和?可以用数据结构进行维护,但询问散块复杂度就上升到 (mathcal O(qBcdot log n))。其实只需要维护每块的前缀和和块与块之间的前缀和,这样单次修改是是 (mathcal O(B+frac{n}{B})) 的,询问降到了 (mathcal O(qB))。总体复杂度是 (mathcal O(qcdot(B+frac{n}{B}))),所以 (B)(sqrt n) 即可。

    需要注意的是,答案范围 (10^5 imes 10^5 imes 10^9=10^{19}),要开 unsigned long long

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-'),write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    } 
    
    #include <cmath>
    #include <iostream>
    using namespace std;
    typedef unsigned long long ll;
    
    const int maxn=1e5+5;
    
    int n,a[maxn],bl[maxn];
    int rk[maxn],cnt,B,L[maxn];
    int R[maxn];
    struct Block {
    	int l,r,num[maxn];
    	ll sum[400],S,ans;
    } s[400];
    
    void getSum(int x) {
    	ll tmp=0;
    	for(int i=s[x].l;i<=s[x].r;++i) {
    		tmp+=a[i];
    		s[x].sum[rk[i]]=tmp;
    	}
    	s[x].sum[B]=tmp;
    	for(int i=x;i<=cnt;++i)	
    		s[i].S=s[i-1].S+s[i].sum[B];
    }
    
    ll Query(int x,int y) {
    	if(bl[x]^bl[y]) {
    		if(bl[x]+1==bl[y])
    			return s[bl[y]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1]+s[bl[x]].sum[B];
    		return s[bl[y]].sum[rk[y]]+s[bl[y]-1].S-s[bl[x]-1].S-s[bl[x]].sum[rk[x]-1];
    	}
    	return s[bl[x]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1];
    }
    
    void getNum(int x) {
    	for(int i=s[x].l;i<=s[x].r;++i) {
    		++s[x].num[L[i]],--s[x].num[R[i]+1];
    		s[x].ans+=Query(L[i],R[i]);
    	}
    	for(int i=1;i<=n;++i)
    		s[x].num[i]+=s[x].num[i-1];
    }
    
    int main() {
    	n=read(9); B=sqrt(n);
    	for(int i=1;i<=n;++i)
    		a[i]=read(9);
    	for(int i=1;i<=n;++i) {
    		L[i]=read(9),R[i]=read(9);
    		bl[i]=(i-1)/B+1;
    		rk[i]=(i-1)%B+1;
    	}
    	cnt=bl[n];
    	for(int i=1;i<=cnt;++i) {
    		s[i].l=s[i-1].r+1;
    		s[i].r=s[i].l+B-1;
    	}
    	s[cnt].r=min(s[cnt].r,n);
    	for(int i=1;i<=cnt;++i)
    		getSum(i);
    	for(int i=1;i<=cnt;++i)
    		getNum(i);
    	int op,x; ll y,dec,Ans;
    	for(int m=read(9);m;--m) {
    		op=read(9),x=read(9),y=read(9);
    		if(op==1) {
    			dec=y-a[x],a[x]=y;
    			getSum(bl[x]);
    			for(int i=1;i<=cnt;++i)
    				s[i].ans+=dec*s[i].num[x];
    		}
    		else {
    			Ans=0;
    			if(bl[x]^bl[y]) {
    				for(int i=x;i<=s[bl[x]].r;++i)
    					Ans+=Query(L[i],R[i]);
    				for(int i=s[bl[y]].l;i<=y;++i)
    					Ans+=Query(L[i],R[i]);
    				for(int i=bl[x]+1;i<bl[y];++i)
    					Ans+=s[i].ans;
    			}
    			else {
    				for(int i=x;i<=y;++i)
    					Ans+=Query(L[i],R[i]);
    			}
    			printf("%llu
    ",Ans);
    		}
    	}
    	return 0;
    }
    

    ( ext{Chef and Problems})

    解法

    好妙啊,预处理 (pre_{i,j},suf_{i,j}) 表示第 (i) 种数在 (j) 块之后/之前(包括第 (j) 块)的最早/最晚出现位置。根据这个可以 (mathtt{dp})(f_{i,j}) 表示 (i,j) 块之间的答案。

    询问时讨论 (l,r) 分别是否在块 ([bl_l+1,bl_r-1]) 之间即可。


    ( ext{Update on 2021.8.17})

    对于序列分块。另外 歴史の研究 也可以用类似的方法分块,存一下块的左右端点可以去掉 (log)

    这样的问题可以考虑此种分块:

    • 可以预处理/快速得出一段连续块之间的答案。
    • 散块的答案可以在 (mathcal O(B)/ mathcal Oleft(frac{n}{B} ight )) 之内拼接。

    好像没啥用,我还是不会

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-'),write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    } 
    
    #include <cmath>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int maxn=1e5+5,inf=1e9;
    
    int n,m,q,a[maxn],bl[maxn],B;
    int pre[maxn][400],suf[maxn][400];
    int f[400][400],Left[maxn];
    
    int main() {
    	memset(pre,0x3f,sizeof pre);
    	n=read(9),m=read(9),q=read(9);
    	B=sqrt(n); 
    	for(int i=1;i<=n;++i)
    		a[i]=read(9),
    		bl[i]=(i-1)/B+1,
    		suf[a[i]][bl[i]]=i;
    	for(int i=n;i>=1;--i)
    		pre[a[i]][bl[i]]=i;
    	for(int i=1;i<=m;++i) {
    		for(int j=bl[n];j>=1;--j)
    			pre[a[i]][j]=min(pre[a[i]][j],pre[a[i]][j+1]);
    		for(int j=1;j<=bl[n];++j)
    			suf[a[i]][j]=max(suf[a[i]][j],suf[a[i]][j-1]);
    	}
    	for(int i=bl[n];i>=1;--i)
    		for(int j=i;j<=bl[n];++j) {
    			f[i][j]=max(f[i+1][j],f[i][j-1]);
    			for(int k=(i-1)*B+1;k<=min(i*B,n);++k)
    				f[i][j]=max(f[i][j],suf[a[k]][j]-k);
    		}
    	int l,r,ans;
    	while(q--) {
    		l=read(9),r=read(9);
    		ans=f[bl[l]+1][bl[r]-1];
    		for(int i=l;i<=min(bl[l]*B,n);++i)
    			ans=max(ans,suf[a[i]][bl[r]-1]-i);
    		for(int i=min(bl[l]*B,n);i>=l;--i)
    			Left[a[i]]=i;
    		for(int i=(bl[r]-1)*B;i<=r;++i) {
    			ans=max(ans,i-pre[a[i]][bl[l]+1]);
    			if(Left[a[i]])
    				ans=max(ans,i-Left[a[i]]);
    		}
    		for(int i=min(bl[l]*B,n);i>=l;--i)
    			Left[a[i]]=0;
    		print(ans,'
    ');
    	}
    	return 0;
    }
    

    ( ext{Children Trips})

    题目大意

    给定 (n) 个点的树,每条边长度是 (1)(2)。有 (m) 个询问,每次给出 (x,y,z),你需要算出 (x ightarrow y) 的路径最少能被划分成多少长度不超过 (z) 的段。

    (n,mle 10^5)

    解法

    考虑计算 (x,y)( m lca),分别跳上去计算答案,最后几步可以拼起来。

    分块可以利用 (z) 的性质:

    • (z>sqrt n)。如果每次跳 (z) 步,那么至多跳 (sqrt n) 次。由此可以直接模拟向上跳的过程,每次花费 (mathcal O(log n)) 跳至多 (z) 步,所以是 (mathcal O(msqrt ncdot log n)) 的。
    • (zle sqrt n)(z) 的种数不超过 (sqrt n)。考虑加速向上跳的过程:预处理一个倍增的数组 (up_{i,j}),初始化 (up_{i,0})(i) 向上跳 (2^0)(z) 到的点。复杂度 (mathcal O(nsqrt ncdot log n+mlog n))

    有个需要注意的点就是不能跳到 ( m lca),最多只能跳到它下面。这是为了处理最后几步的情况。

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-'),write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    } 
    
    #include <cmath>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef pair <int,int> pii;
    
    const int maxn=1e5+5;
    
    int n,m,head[maxn],cnt,M;
    int ans[maxn],s_cnt,b_cnt;
    int f[maxn][20],dep[maxn];
    int up[maxn][20],dis[maxn];
    struct node {
    	int x,y,p,id;
    	
    	bool operator < (const node &t) const {
    		return p<t.p;
    	}
    } S[maxn],B[maxn];
    struct Edge {
    	int nxt,to,w;
    } e[maxn<<1];
    
    void addEdge(int u,int v,int w) {
    	e[++cnt].w=w;
    	e[cnt].to=v;
    	e[cnt].nxt=head[u];
    	head[u]=cnt;
    	e[++cnt].w=w;
    	e[cnt].to=u;
    	e[cnt].nxt=head[v];
    	head[v]=cnt;
    }
    
    int Lca(int x,int y) {
    	if(dep[x]<dep[y])	
    		swap(x,y);
    	for(int i=18;i>=0;--i)
    		if(dep[f[x][i]]>=dep[y])
    			x=f[x][i];
    	if(x==y) return x;
    	for(int i=18;i>=0;--i)
    		if(f[x][i]^f[y][i]) {
    			x=f[x][i];
    			y=f[y][i];
    		}
    	return f[x][0];
    }
    
    int jump(int x,int p) {
    	int rec=x;
    	for(int i=18;i>=0;--i)
    		if(dis[rec]-dis[f[x][i]]<=p)
    			x=f[x][i];
    	return x;
    }
    
    pii brute(int x,int y,int p) {
    	pii r=make_pair(0,0);
    	while(x^y) {
    		int z=jump(x,p);
    		if(dep[z]>dep[y])	
    			++r.first,
    			x=z;
    		else 
    			r.second+=dis[x]-dis[y],x=y;
    	}
    	return r;
    }
    
    pii Jump(int x,int y,int p) {
    	pii r=make_pair(0,0);
    	for(int i=18;i>=0;--i)
    		if(dep[up[x][i]]>dep[y])
    			x=up[x][i],
    			r.first+=(1<<i);
    	r.second=dis[x]-dis[y];
    	return r;
    }
    
    void init(int p) {
    	for(int i=1;i<=n;++i)
    		up[i][0]=jump(i,p);
    	for(int j=1;j<=18;++j)
    		for(int i=1;i<=n;++i)
    			up[i][j]=up[up[i][j-1]][j-1];
    }
    
    void calc(const node &t) {
    	if(t.x==t.y) {
    		ans[t.id]=0;
    		return;
    	}
    	int lca=Lca(t.x,t.y);
    	pii r1,r2;
    	if(t.p>M) {
    		r1=brute(t.x,lca,t.p);
    		r2=brute(t.y,lca,t.p);
    	}
    	else {
    		r1=Jump(t.x,lca,t.p);
    		r2=Jump(t.y,lca,t.p);
    	}
    	ans[t.id]=r1.first+r2.first;
    	if(r1.second+r2.second<=t.p)
    		++ans[t.id];
    	else ans[t.id]+=2;
    }
    
    void b_solve() {
    	for(int i=1;i<=b_cnt;++i) 
    		calc(B[i]);
    }
    
    void s_solve() {
    	sort(S+1,S+s_cnt+1);
    	for(int i=1;i<=s_cnt;++i) {
    		if(i==1 or (S[i].p^S[i-1].p))
    			init(S[i].p);
    		calc(S[i]);
    	}
    }
    
    void dfs(int u,int fa) {
    	f[u][0]=fa,dep[u]=dep[fa]+1;
    	for(int i=1;i<=18;++i)
    		f[u][i]=f[f[u][i-1]][i-1];
    	for(int i=head[u];i;i=e[i].nxt)
    		if(e[i].to^fa)
    			dis[e[i].to]=dis[u]+e[i].w,
    			dfs(e[i].to,u);
    }
    
    int main() {
    	n=read(9); M=sqrt(n);
    	int u,v,w;
    	for(int i=1;i<n;++i) {
    		u=read(9),v=read(9),w=read(9);
    		addEdge(u,v,w);
    	}
    	m=read(9); dfs(1,0);
    	for(int i=1;i<=m;++i) {
    		u=read(9),v=read(9),w=read(9);
    		if(w>M) 
    			B[++b_cnt]=(node){u,v,w,i};
    		else
    			S[++s_cnt]=(node){u,v,w,i};
    	}
    	b_solve(),s_solve();
    	for(int i=1;i<=m;++i)
    		print(ans[i],'
    ');
    	return 0;
    }
    
  • 相关阅读:
    RPA-UiPath视频教程2
    skywalking链路监控
    RPA-UiPath视频教程1
    Redis基础与性能调优
    tomcat调优
    DDos、CC攻击与防御
    UiPath官方视频Level2
    UiPath直播课程
    UiPath手把手教程
    【0】python核心编程,第二章
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15139575.html
Copyright © 2011-2022 走看看