带修树上莫队
解题
该题理解题意后即为求一条简单路径上
用树上莫队转化为链式结构解决,发现做两次贡献为0可以用 异或 解决,再用带修莫队维护即可
注意一些小细节即可
代码及注释
#include<bits/stdc++.h>
#define int long long
#define B(x) ((x+bs-1)/bs)
using namespace std;
const int N=2e5+10,M=26;
int f[N][M+1],h[N*2],idx,v[N],w[N];
int dfn[N],timeset,st[N],en[N],a[N];
int temp,vis[N],cnt[N];
int dep[N];
int n,m,Q,mcnt,qcnt;
int bs;
struct ques{
int l,r,id,time,lca;
}q[N*2];
struct modify{
int x,p;
}modi[N*2];
struct edge{
int v,next;
}e[2*N];
void con(int u,int v){
e[++idx].v=v;
e[idx].next=h[u];
h[u]=idx;
}
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
st[u]=++timeset;
dfn[timeset]=u;
f[u][0]=fa;
for(int i=1;i<M;i++){
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=h[u];i;i=e[i].next){
int v=e[i].v;
if(v!=fa) dfs(v,u);
}
en[u]=++timeset;
dfn[timeset]=u;
}//树上莫队及LCA预处理
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=M-1;i>=0;i--){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=M-1;i>=0;i--){
if(f[x][i]!=f[y][i]) {
x=f[x][i],y=f[y][i];
}
}
return f[x][0];
}
bool cmp(ques x,ques y){
if(B(x.l)!=B(y.l)) return x.l<y.l;
if(B(x.r)!=B(y.r)) return x.r<y.r;
return x.time<y.time;
}//询问排序
void add(int x){
cnt[x]++;
temp+=v[x]*w[cnt[x]];
}
void del(int x) {
temp-=v[x]*w[cnt[x]];
cnt[x]--;
}
void lcd(int pos){
vis[pos]^=1;
if(vis[pos]==1) add(a[pos]);
else del(a[pos]);
}//括号序用异或处理两次进入
void modif(int t) {
if(vis[modi[t].p]) lcd(modi[t].p),swap(modi[t].x,a[modi[t].p]),lcd(modi[t].p);
//删除一个节点只需要让他再异或一次即可,添加亦然
else swap(modi[t].x,a[modi[t].p]);
}//修改数组
int ans[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m>>Q;
bs=pow(2*n,2.0/3.0);
for(int i=1;i<=m;i++){
cin>>v[i];
}
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<n;i++) {
int u,v;
cin>>u>>v;
con(u,v),con(v,u);
}
dfs(1,0);
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(Q--){
int opt,x,y;
cin>>opt>>x>>y;
if(opt==0) modi[++mcnt]={y,x};
else{
int lc=lca(x,y);
if(lc==x||lc==y) {
if(st[x]>st[y]) swap(x,y);
q[++qcnt]={st[x],st[y],qcnt,mcnt,0};
}
else {
if(en[x]>st[y]) swap(x,y);
q[++qcnt]={en[x],st[y],qcnt,mcnt,lc};
}
}
}
sort(q+1,q+qcnt+1,cmp);
int l=1,r=0,tt=0;
for(int i=1;i<=qcnt;i++){
while(l>q[i].l) lcd(dfn[--l]);
while(r<q[i].r) lcd(dfn[++r]);
while(l<q[i].l) lcd(dfn[l++]);
while(r>q[i].r) lcd(dfn[r--]);
while(tt<q[i].time) modif(++tt);
while(tt>q[i].time) modif(tt--);
if(q[i].lca) lcd(q[i].lca);
ans[q[i].id]=temp;
if(q[i].lca) lcd(q[i].lca);
}
for(int i=1;i<=qcnt;i++) cout<<ans[i]<<'\n';
return 0;
}
莫队加值域分块
例题1Luogu P4867
题意
给定长为 n 的序列,n<=1e5,a[i]∈[1,n] ,给出 m 个询问(m<=1e6),每次查询一段区间内值在a~b中的数的种数
比如对于序列 5 2 3 4 1 ,询问 { l = 2 , r = 4 , a = 1 , b = 3 } ,答案为 2 (区间2~4内权值在1~3的有2和3两种)
解决
如果没有 a、b 的限制,那么是一道简单的板子莫队,那么加上权值的限制后,我们要怎样操作?
1、考虑继续用莫队实现:
对于限制{a,b} ,想想权值类的数据结构优化。莫队本身带有 的复杂度,已经是上限了,所以这个维护只能是常数级别(显然没有)或能跟莫队完美结合。
我们发现莫队是有分块操作的,那我们自然想到值域分块
2、值域分块:
顾名思义,这里按值域将1~n分成块长为根号n的块。设数组 sum[i]表示第 i 块中现有不同数字的种类。我们可以在莫队的 add 和 del 操作中轻易维护处 sum 数组
3、怎样记录答案?
显然不能简单的以莫队里l~r中数字种类个数作为答案。对于{a,b},单独处理 a、b所在的块(直接枚举 莫队cnt 数组),中间的累加 sum[i] ,就能得到答案。
显然时间复杂度为根号级别,完成!
代码及解释
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,M=1e3+10;
int n,m,bs;
int a[N],b[N],cnt[N],sum[M],ans[N],bl[M],br[M];
struct que{
int id,l,r,a,b;
}q[N];
bool cmp(que x,que y){
if(b[x.l]!=b[y.l]) return x.l<y.l;
if(b[x.l]&1) return x.r<y.r;
return x.r>y.r;
}
void add(int x){
x=a[x];
cnt[x]++;
if(cnt[x]==1) sum[b[x]]++;
}
void del(int x){
x=a[x];
cnt[x]--;
if(cnt[x]==0) sum[b[x]]--;
}
void quer(int i){
int l=q[i].a,r=q[i].b,id=q[i].id;
for(int i=l;i<=r&&i<=br[b[l]];i++) if(cnt[i]) ans[id]++;//枚举a所在块
if(b[l]!=b[r]) for(int i=bl[b[r]];i<=r;i++) if(cnt[i])ans[id]++; //枚举b所在块
for(int i=b[l]+1;i<b[r];i++) ans[id]+=sum[i];//中间的剩余部分
//复杂度根号n
}
signed main(){
cin>>n>>m;
bs=sqrt(n);
for(int i=1;i<=n;i++) {
cin>>a[i];
b[i]=(i-1)/bs+1;//值域分块和莫队的块是一样的
}
for(int i=1;i<=b[n];i++) {
bl[i]=(i-1)*bs+1,br[i]=i*bs;//需要记录每个块的左右端点
}
for(int i=1;i<=m;i++) {
cin>>q[i].l>>q[i].r>>q[i].a>>q[i].b;
q[i].id=i;
}
br[b[n]]=n;
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++) {
int L=q[i].l,R=q[i].r;
while(l>L) add(--l);
while(r<R) add(++r);
while(l<L) del(l++);
while(r>R) del(r--);
quer(i);//算答案
}
for(int i=1;i<=m;i++)
cout<<ans[i]<<endl;
return 0;
}
练习 Luogu曼哈顿交易
tips:这题千万注意审题!!!
完结撒花❀❀❀