(笔记)线段树分治

本质上是一种 trick,利用了线段树的结构,做时间轴上的分治。具体来说,可能存在这一类问题,它需要在时间段 \([1,k]\) 上维护一个东西,而维护的方法是方便加入、方便撤销、不方便删除的(如并查集),这时候我们可以用到线段树分治来解决这类问题,将修改-查询-删除的解法以一个 \(\log\) 的代价替换为修改-查询-撤销的。

  1. 对于区间修改,单点查询,我们把修改区间 \([l,r]\) 拆成 \(\log k\) 个区间挂到线段树上,然后查询在叶子节点完成,每次完成该节点的修改后递归需要撤销,这里对于修改是 \(O(\log k)\) 的,对于查询是 \(O(1)\) 的(假设计算答案的数据结构为 \(O(1)\) 的,但是绝大多数情况都不可能)。

  2. 对于单点修改,区间查询,我们对应把查询区间 \([l,r]\) 拆成 \(\log k\) 个区间挂到线段树上,修改对应线段树从根节点到对应叶子节点路径上的每一个点,里面的信息都要进行修改,查询就在每个对应节点上完成,这里对于修改是 \(O(\log k)\) 的,对于查询也是 \(O(\log k)\) 的(假设计算答案的数据结构为 \(O(1)\) 的,但是绝大多数情况都不可能)。

  3. 对于区间修改,区间查询,我们好像无能为力,真抱歉:(。

例题

CF601E A Museum Robbery

比模板更适合做板子,观察到计算答案贡献就相当于一个 01 背包,对于操作作用时间 \([l,r]\) 拆分为 \(\log n\) 个区间,然后单点查询的线段树分治即可,答案暴力算,对于撤销每个节点用一个 vector 或手写 stack 皆可,直接做时间维线段树分治,时间复杂度约 \(O(n^2\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5;
const LL MOD=1e9+7,P=1e7+19;
int n,k,qn,tms=1,cnt;
struct Q{int v,w,l,r;}q[N];
LL dp[N];
vector<int>G[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void update(int p,int l,int r,int L,int R,int id){
  if(L<=l&&r<=R){G[p].push_back(id);return ;}
  if(L<=mid)update(ls,l,mid,L,R,id);
  if(R>mid)update(rs,mid+1,r,L,R,id);
}
void solve(int p,int l,int r){
  vector<pair<int,int> >bk;
  for(int g:G[p]){
    int v=q[g].v,w=q[g].w;
    for(int i=k;i>=w;i--){
      if(dp[i-w]+v>dp[i]){
        bk.push_back(make_pair(i,dp[i]));
        dp[i]=dp[i-w]+v;
      }
    }
  }
  if(l==r){
    LL ans=0,pw=1;
    for(int m=1;m<=k;m++){
      ans=(ans+dp[m]*pw%MOD)%MOD;
      pw=pw*P%MOD;
    }
    cout<<ans<<'\n';
    for(int ID=bk.size()-1;ID>=0;ID--){
      auto g=bk[ID];
      dp[g.first]=g.second;
    }
    return ;
  }
  solve(ls,l,mid);
  solve(rs,mid+1,r);
  for(int ID=bk.size()-1;ID>=0;ID--){
    auto g=bk[ID];
    dp[g.first]=g.second;
  }
}
#undef ls
#undef rs
#undef mid
int main(){
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  cin>>n>>k;
  for(int i=1;i<=n;i++){
    cin>>q[i].v>>q[i].w;
    q[i].l=1;q[i].r=-1;
  }
  cnt=n;
  cin>>qn;
  for(int i=1;i<=qn;i++){
    int op,v,w,x;cin>>op;
    if(op==1){
      cin>>v>>w;
      q[++cnt]=(Q){v,w,tms,-1};
    }
    else if(op==2){
      cin>>x;
      q[x].r=tms-1;
    }
    else tms++;
  }
  tms--;
  for(int i=1;i<=cnt;i++){
    if(q[i].r==-1)q[i].r=tms;
    if(q[i].l<=q[i].r)
      update(1,1,tms,q[i].l,q[i].r,i);
  }
  solve(1,1,tms);
  return 0;
}

P5787 二分图 /【模板】线段树分治

我们需要对每条边维护存在区间 \([l,r]\),并在每个时间点判断其是否为二分图。判断二分图的方法可采用扩展域并查集(类似 2-SAT 的思想),对每个点 \(i\),将其拆成两个正点和反点,每次连边的时候分别将两个节点的正反点依次相连。判断非法的方法是如果存在一个点的正点和反点在一个连通块内,那么这个图不是二分图。

我们可以把 \([l,r]\)(修改区间)拆成 \(\log k\) 个区间挂到线段树上,然后线段树上遍历即可。需要注意的是每次在 \(u\) 加入一个操作后,在递归结束要撤销该操作。线段树遍历加上可撤销并查集(按秩合并)总共是 \(O(k\log n\log k)\) 的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+5;
int n,m,k;
struct opt{int x,y;}e[N];
vector<int>vec[N<<2];
struct Uni{
	int fa[N],tp,siz[N];
	PII stk[N*2];
	void init(){for(int i=1;i<=n*2;i++)fa[i]=i,siz[i]=1;}
	int fr(int x){
		if(fa[x]==x)return x;
		return fr(fa[x]);
	}
	bool isa(int x,int y){return fr(x)==fr(y);}
	void ins(int x,int y){
		int frx=fr(x),fry=fr(y);
		if(frx==fry)return ;
		if(siz[frx]<siz[fry]){
			swap(frx,fry);
			swap(x,y);
		}
		fa[fry]=frx;
		siz[frx]+=siz[fry];
		stk[++tp]=make_pair(frx,fry);
	}
	void undo(int lim){
		while(tp>lim){
			int frx=stk[tp].first;
			int fry=stk[tp].second;
			siz[frx]-=siz[fry];
			fa[fry]=fry;
			tp--;
		}
	}
}U;
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void update(int p,int l,int r,int L,int R,int id){
	if(l>R||r<L)return ;
	if(L<=l&&r<=R){
		vec[p].push_back(id);
		return ;
	}
	if(L<=mid)update(ls,l,mid,L,R,id);
	if(R>mid)update(rs,mid+1,r,L,R,id);
}
void solve(int p,int l,int r){
	int rcd=U.tp;
	for(int v:vec[p]){
		int x=e[v].x,y=e[v].y;
		if(U.isa(x,y)){
			for(int i=l;i<=r;i++)
				printf("No\n");
			U.undo(rcd);
			return ;
		}
		U.ins(x+n,y);U.ins(x,y+n);
	}
	if(l==r){
		printf("Yes\n");
		U.undo(rcd);
		return ;
	}
	solve(ls,l,mid);
	solve(rs,mid+1,r);
	U.undo(rcd);
}
#undef ls
#undef rs
#undef mid
int main(){
	scanf("%d%d%d",&n,&m,&k);
	U.init();
	for(int i=1;i<=m;i++){
		int x,y,l,r;
		scanf("%d%d%d%d",&x,&y,&l,&r);
		l++;
		e[i]=(opt){x,y};
		if(l>r)continue;
		update(1,1,k,l,r,i);
	}
	solve(1,1,k);
	return 0;
}

P11619 [PumpkinOI Round 1] 种南瓜

显然这是一个区间修改单点查询的问题,和模板几乎一样,可以用两棵线段树分别维护当前下标为左端点最大的右端点(\(T_1\))和当前下标为右端点的最小左端点(\(T_2\)),只需要单点修改、撤销和区间查询即可,对于 \([l,r]\) 查询 \(T_1\) 中是否存在 \([l+1,r]\) 上比 \(r\) 大的数,查询 \(T_2\) 中是否存在比 \([l,r-1]\) 上比 \(l\) 小的数就可以判断是否非法。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,qn;
struct Op{int l,r,L,R;}q[N];
vector<int>G[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
int mx[N<<2],mn[N<<2];
void pushup(int p){mx[p]=max(mx[ls],mx[rs]);}
void pushup1(int p){mn[p]=min(mn[ls],mn[rs]);}
void build(int p,int l,int r){
  if(l==r){mn[p]=n+1;return ;}
  build(ls,l,mid);
  build(rs,mid+1,r);
  pushup1(p);
}
void modify(int p,int l,int r,int pos,int v){
  if(l==r){mx[p]=max(mx[p],v);return ;}
  if(pos<=mid)modify(ls,l,mid,pos,v);
  else modify(rs,mid+1,r,pos,v);
  pushup(p);
}
void modify1(int p,int l,int r,int pos,int v){
  if(l==r){mn[p]=min(mn[p],v);return ;}
  if(pos<=mid)modify1(ls,l,mid,pos,v);
  else modify1(rs,mid+1,r,pos,v);
  pushup1(p);
}
void cover(int p,int l,int r,int pos,int v){
  if(l==r){mx[p]=v;return ;}
  if(pos<=mid)cover(ls,l,mid,pos,v);
  else cover(rs,mid+1,r,pos,v);
  pushup(p);
}
void cover1(int p,int l,int r,int pos,int v){
  if(l==r){mn[p]=v;return ;}
  if(pos<=mid)cover1(ls,l,mid,pos,v);
  else cover1(rs,mid+1,r,pos,v);
  pushup1(p);
}
int quemx(int p,int l,int r,int L,int R){
  if(L>R)return 0;
  if(L<=l&&r<=R)return mx[p];
  if(L<=mid&&R>mid)return max(quemx(ls,l,mid,L,R),quemx(rs,mid+1,r,L,R));
  if(L<=mid)return quemx(ls,l,mid,L,R);
  return quemx(rs,mid+1,r,L,R);
}
int quemn(int p,int l,int r,int L,int R){
  if(L>R)return n+1;
  if(L<=l&&r<=R)return mn[p];
  if(L<=mid&&R>mid)return min(quemn(ls,l,mid,L,R),quemn(rs,mid+1,r,L,R));
  if(L<=mid)return quemn(ls,l,mid,L,R);
  return quemn(rs,mid+1,r,L,R);
}
void update(int p,int l,int r,int L,int R,int v){
  if(L<=l&&r<=R){G[p].push_back(v);return ;}
  if(L<=mid)update(ls,l,mid,L,R,v);
  if(R>mid)update(rs,mid+1,r,L,R,v);
}
void solve(int p,int l,int r,bool cont){
  bool cgd=0;
  vector<int>bk;
  if(cont){
    for(int i:G[p]){
      int L=quemx(1,1,n,q[i].l+1,q[i].r);
      int R=quemn(1,1,n,q[i].l,q[i].r-1);
      if(L>q[i].r)cont=0,cgd=1;
      if(R<q[i].l)cont=0,cgd=1;
      bk.push_back(quemx(1,1,n,q[i].l,q[i].l));
      modify(1,1,n,q[i].l,q[i].r);
      bk.push_back(quemn(1,1,n,q[i].r,q[i].r));
      modify1(1,1,n,q[i].r,q[i].l);
    }
  }
  if(l==r){
    if(cont)cout<<"yeS\n";
    else cout<<"nO\n";
    if(cont||cgd){
      int pos=-1;
      for(int i:G[p]){
        int vp=bk[++pos];
        cover(1,1,n,q[i].l,vp);
        vp=bk[++pos];
        cover1(1,1,n,q[i].r,vp);
      }
    }
    
    return ;
  }
  solve(ls,l,mid,cont);
  solve(rs,mid+1,r,cont);
  if(cont||cgd){
    int pos=-1;
    for(int i:G[p]){
      int vp=bk[++pos];
      cover(1,1,n,q[i].l,vp);
      vp=bk[++pos];
      cover1(1,1,n,q[i].r,vp);
    }
  }
  
}
#undef ls
#undef rs
#undef mid
int main(){
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  cin>>n>>qn;
  for(int i=1;i<=qn;i++){
    int op;cin>>op;
    if(op==1){
      int l,r;cin>>l>>r;
      q[i]=(Op){l,r,i,qn};
    }
    else {
      int x;cin>>x;
      q[x].R=i-1;
    }
  }
  for(int i=1;i<=qn;i++)
    if(q[i].l)update(1,1,qn,q[i].L,q[i].R,i);
  build(1,1,n);
  solve(1,1,qn,1);
  return 0;
}

P4585 [FJOI2015] 火星商店问题

本题的题意是每出现一个 \(0\) 操作算一天。

我们依旧是进行时间轴分治,这里在时间轴上的单点修改保证了我们在分治结构上合并信息时的复杂度为 \(O(D\log D)\)(其中 \(D\) 为天数)。我们对于线段树上每个节点用一个可持久化 01 trie 维护信息,显然每个单点修改会对 \(O(\log D)\) 个节点产生影响,我们每次在节点上处理的时候单独加入该修改,可持久化线段树只有当前节点所具有的修改数个版本,每修改一次复杂度就是 \(O(\log D\log V)\)\(V\) 为值域)的,总共 \(O(D\log D\log V)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,mL[N],mR[N];
int rt[N],ncnt,tms,ans[N],qcnt;
struct Tre{int ch[2],cnt;}t[N*18];
struct Obj{int id,v,tms;}q1[N],dt[N];
struct Qry{int l,r,x;}q2[N];
vector<int>G[N<<2];
bool cmp(Obj x,Obj y){return x.id<y.id;}
void insert(int o,int &p,int v,int id){
  if(!p)p=++ncnt;
  t[p]=t[o];t[p].cnt++;
  if(id<=-1)return ;
  t[p].ch[(v>>id)&1]=0;
  insert(t[o].ch[(v>>id)&1],t[p].ch[(v>>id)&1],v,id-1);
}
int query(int o,int p,int v,int id){
  if(id<=-1)return 0;
  bool ex=(v>>id)&1;
  if(t[t[p].ch[ex^1]].cnt-t[t[o].ch[ex^1]].cnt>0)
    return (1<<id)+query(t[o].ch[ex^1],t[p].ch[ex^1],v,id-1);
  return query(t[o].ch[ex],t[p].ch[ex],v,id-1);
}
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void update(int p,int l,int r,int L,int R,int v){
  if(L>R||R<l||L>r)return ;
  if(L<=l&&r<=R){G[p].push_back(v);return ;}
  if(L<=mid)update(ls,l,mid,L,R,v);
  if(R>mid)update(rs,mid+1,r,L,R,v);
}
int stk[N],tp;
void solve(int p,int l,int r,int L,int R){
  if(L>R||l>r)return ;
  if(G[p].size()){
  	tp=0;ncnt=0;
	  for(int i=L;i<=R;i++){
	    stk[++tp]=q1[i].id;rt[tp]=0;
	    insert(rt[tp-1],rt[tp],q1[i].v,16);
	  }
	  for(int v:G[p]){
	    int l=q2[v].l,r=q2[v].r,x=q2[v].x;
	    l=upper_bound(stk+1,stk+1+tp,l-1)-(stk+1);
	    r=upper_bound(stk+1,stk+1+tp,r)-(stk+1);
	    ans[v]=max(ans[v],query(rt[l],rt[r],x,16));
	  }
  }
  if(l==r)return ;
  int pos=L-1,MID=L;
  for(int i=L;i<=R;i++)
    if(q1[i].tms<=mid)dt[++pos]=q1[i];
  MID=pos;
  for(int i=L;i<=R;i++)
    if(q1[i].tms>mid)dt[++pos]=q1[i];
  for(int i=L;i<=R;i++)
    q1[i]=dt[i];
  solve(ls,l,mid,L,MID);
  solve(rs,mid+1,r,MID+1,R);
}
#undef ls
#undef rs
#undef mid
int main(){
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    int v;cin>>v;
    insert(rt[i-1],rt[i],v,16);
  }
  for(int i=1;i<=m;i++){
    int op;cin>>op;
    if(!op){
      int s,v;cin>>s>>v;
      q1[++tms]=(Obj){s,v,tms};
    }
    else {
      int l,r,x,d;
      cin>>l>>r>>x>>d;
      mL[++qcnt]=max(1,tms-d+1);mR[qcnt]=tms;
      ans[qcnt]=query(rt[l-1],rt[r],x,16);
      q2[qcnt]=(Qry){l,r,x};
    }
  }
  for(int i=1;i<=qcnt;i++)
    update(1,1,tms,mL[i],mR[i],i);
  sort(q1+1,q1+1+tms,cmp);
  solve(1,1,tms,1,tms);
  for(int i=1;i<=qcnt;i++)
    cout<<ans[i]<<'\n';
  return 0;
}

P3206 [HNOI2010] 城市建设

注意到难以维护动态改边权(先删再加)上做最小生成树,因为如果边权变大无法迅速找到可以将其替换的边。但是维护动态加边(只加)是容易的,根据方便撤销不方便删除的特性,我们套用线段树分治,在时间轴分治,每个节点动态最小生成树用 LCT 维护即可,每次加边找环上最大边权,如果小于就将其替换。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e4+5;
int ncnt,n,m,q,ide[N],mp[N*2];
LL ans;
struct Node{int ch[2],fa,id,val,mx;bool rev;}t[N*3];
bool isRoot(int x){return !(t[t[x].fa].ch[0]==x||t[t[x].fa].ch[1]==x);}
void pushup(int p){
  t[p].mx=max(t[p].val,max(t[t[p].ch[0]].mx,t[t[p].ch[1]].mx));
  if(t[p].mx==t[p].val)t[p].id=p;
  else if(t[p].mx==t[t[p].ch[0]].mx)t[p].id=t[t[p].ch[0]].id;
  else t[p].id=t[t[p].ch[1]].id;
}
void rev(int p){
  if(!p)return ;
  t[p].rev^=1;
  swap(t[p].ch[0],t[p].ch[1]);
}
void pushdown(int p){
  if(t[p].rev){
    rev(t[p].ch[0]);
    rev(t[p].ch[1]);
    t[p].rev=0;
  }
}
void push(int x){
  if(!isRoot(x))push(t[x].fa);
  pushdown(x);
}
void rotate(int x){
  int y=t[x].fa,z=t[y].fa;
  if(!isRoot(y))t[z].ch[t[z].ch[1]==y]=x;
  bool k=(t[y].ch[1]==x);
  if(t[x].ch[k^1])t[t[x].ch[k^1]].fa=y;
  t[y].ch[k]=t[x].ch[k^1];
  t[x].ch[k^1]=y;
  t[y].fa=x;t[x].fa=z;
  pushup(y);
}
void splay(int x,int S){
  int y,z;
  push(x);
  while(!isRoot(x)){
    y=t[x].fa,z=t[y].fa;
    if(!isRoot(y))
      ((t[z].ch[1]==y)^(t[y].ch[1]==x))?rotate(x):(S==z?rotate(x):rotate(y));
    rotate(x);
  }
  pushup(x);
}
void access(int x){
  for(int lst=0;x;lst=x,x=t[x].fa){
    splay(x,0);
    t[x].ch[1]=lst;
    pushup(x);
  }
}
void makeroot(int x){
  access(x);
  splay(x,0);
  rev(x);
}
int findroot(int x){
  access(x);
  splay(x,0);
  while(t[x].ch[0])x=t[x].ch[0];
  return x;
}
void link(int x,int y){
  makeroot(x);
  t[x].fa=y;
}
void cut(int x,int y){
  makeroot(y);
  access(x);
  splay(x,y);
  t[y].fa=t[x].ch[0]=0;
  pushup(x);
}
pair<int,int> query(int x,int y){
  makeroot(y);
  access(x);
  splay(x,y);
  return make_pair(t[x].mx,t[x].id);
}
int cnt,lat[N],rX[N],rY[N];
struct Q{int l,r,k,d;}qf[N*2];
vector<int>G[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void update(int p,int l,int r,int L,int R,int v){
  if(L<=l&&r<=R){G[p].push_back(v);return ;}
  if(L<=mid)update(ls,l,mid,L,R,v);
  if(R>mid)update(rs,mid+1,r,L,R,v);
}
void solve(int p,int l,int r){
  vector<pair<int,int> >bk;
  for(int v:G[p]){
    int i=qf[v].k;
    int x=rX[i],y=rY[i];
    int frx=findroot(x),fry=findroot(y);
    t[ide[i]].val=t[ide[i]].mx=qf[v].d;
    t[ide[i]].id=i;
    if(frx!=fry){
      link(ide[i],y),link(x,ide[i]);
      ans+=qf[v].d,bk.push_back(make_pair(i,0));
    }
    else {
      pair<int,int>P=query(x,y);
      if(P.first>t[ide[i]].val){
        ans-=P.first;ans+=t[ide[i]].val;
        int j=mp[P.second];
        cut(rX[j],ide[j]);
        cut(ide[j],rY[j]);
        bk.push_back(make_pair(j,1));
        link(ide[i],y),link(x,ide[i]);
        bk.push_back(make_pair(i,0));
      }
    }
  }
  if(l==r){
    cout<<ans<<'\n';
    for(int ID=bk.size()-1;ID>=0;ID--){
      pair<int,int>h=bk[ID];
      bool st=h.second;
      int i=h.first;
      int x=rX[i],y=rY[i];
      if(!st){
        ans-=t[ide[i]].val;
        cut(x,ide[i]);
        cut(ide[i],y);
      }
      else {
        link(ide[i],y),link(x,ide[i]);
        ans+=t[ide[i]].val;
      }
    }
    for(int v:G[p]){
      int i=qf[v].k;
      t[ide[i]].val=t[ide[i]].mx=0;
      t[ide[i]].id=0;
    }
    return ;
  }
  solve(ls,l,mid);
  solve(rs,mid+1,r);
  for(int ID=bk.size()-1;ID>=0;ID--){
    pair<int,int>h=bk[ID];
    bool st=h.second;
    int i=h.first;
    int x=rX[i],y=rY[i];
    if(!st){
      ans-=t[ide[i]].val;
      cut(x,ide[i]);
      cut(ide[i],y);
    }
    else {
      link(ide[i],y),link(x,ide[i]);
      ans+=t[ide[i]].val;
    }
  }
  for(int v:G[p]){
    int i=qf[v].k;
    t[ide[i]].val=t[ide[i]].mx=0;
    t[ide[i]].id=0;
  }
}
#undef ls
#undef rs
#undef mid
int main(){
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  cin>>n>>m>>q;ncnt=n;
  for(int i=1;i<=m;i++){
    int x,y,w;
    cin>>x>>y>>w;
    rX[i]=x,rY[i]=y;
    ide[i]=++ncnt;
    mp[ncnt]=i;
    lat[i]=i;
    qf[++cnt]=(Q){1,q,i,w};
  }
  for(int i=1;i<=q;i++){
    int k,d;cin>>k>>d;
    qf[lat[k]].r=i-1;
    qf[++cnt]=(Q){i,q,k,d};
    lat[k]=cnt;
  }
  for(int i=1;i<=cnt;i++)
    if(qf[i].l<=qf[i].r)update(1,1,q,qf[i].l,qf[i].r,i);
  solve(1,1,q);
  return 0;
}

P3733 [HAOI2017] 八纵八横

笛卡尔积题。吗?考虑可重路径异或组合相当于树上所有经过根节点 \(1\) 的环的异或组合,我们可以随便搞一个生成树,然后对于每个这样的环(也可以是折返路径)计算其异或和,丢进线性基里面处理求异或最大值即可,本题还应使用 bitset 维护边权。注意到一条边有加入和删除时间,我不会可删除线性基!考虑线段树分治,然后撤销是容易的,区间修改和单点查询,直接无脑套用即可。时间复杂度乱七八糟的我也不知道是啥 \(O(\text{能过})\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e3+5;
typedef bitset<N> B;
int n,m,Q,cnth;
int fa[N];
bool exi[N];
B srt[N],pf[N],Z[N];
int fr(int x){return fa[x]==x?x:fa[x]=fr(fa[x]);}
bool ins(int x,int y){
  int frx=fr(x),fry=fr(y);
  if(frx==fry)return 0;
  fa[frx]=fry;return 1;
}
struct Edge{int v,next;B w;}E[N],e[N<<1];
struct Cir{int l,r;B w;}P[N];
int head[N],idx;
void ins1(int x,int y,B &z){
  e[++idx].v=y;
  e[idx].next=head[x];
  e[idx].w=z;
  head[x]=idx;
}
void dfs(int u,int fa){
  for(int i=head[u];i;i=e[i].next){
    int v=e[i].v;
    if(v==fa)continue;
    srt[v]=srt[u]^e[i].w;
    dfs(v,u);
  }
}
pair<int,B>ins2(B x){
  for(int i=1000;i>=0;i--){
    if(x[i]){
      if(!exi[i]){B pre=pf[i];pf[i]=x;exi[i]=1;return make_pair(i,pre);}
      else x^=pf[i];
    }
  }
  return make_pair(-1,(B)0);
}
int lat[N];
char opt[7];
vector<int>G[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void print(B &x){
  bool tf=0;
  for(int i=1000;i>=0;i--){
    if(x[i]){tf=1;cout<<'1';}
    else if(tf)cout<<'0';
  }
  if(!tf)cout<<'0';
  cout<<'\n';
}
void update(int p,int l,int r,int L,int R,int v){
  if(L<=l&&r<=R){G[p].push_back(v);return ;}
  if(L<=mid)update(ls,l,mid,L,R,v);
  if(R>mid)update(rs,mid+1,r,L,R,v);
}
void solve(int p,int l,int r){
  vector<pair<int,B> >bk;
  for(int v:G[p]){
    pair<int,B>g=ins2(P[v].w);
    if(g.first!=-1)bk.push_back(g);
  }
  if(l==r){
    B ans(0);
    for(int i=1000;i>=0;i--)
      if(exi[i]&&!ans[i])ans^=pf[i];
    print(ans);
    for(auto v:bk){
      int i=v.first;
      pf[i]=v.second,exi[i]=0;
    }
    return ;
  }
  solve(ls,l,mid);
  solve(rs,mid+1,r);
  for(auto v:bk){
    int i=v.first;
    pf[i]=v.second,exi[i]=0;
  }
}
#undef ls
#undef rs
#undef mid
int main(){
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  cin>>n>>m>>Q;
  for(int i=1;i<=n;i++)fa[i]=i;
  for(int i=1;i<=m;i++){
    int u,v;
    string p;
    cin>>u>>v>>p;
    B w(p);
    if(ins(u,v)){
      ins1(u,v,w);
      ins1(v,u,w);
    }
    else E[++cnth]=(Edge){u,v,w};
  }
  dfs(1,0);
  for(int i=1;i<=cnth;i++)
    E[i].w^=srt[E[i].next]^srt[E[i].v],
    ins2(E[i].w);
  cnth=0;
  int rcnth=0;
  for(int i=1;i<=Q;i++){
    cin>>opt;
    if(opt[0]=='A'){
      int x,y;
      string p;
      cin>>x>>y>>p;
      B z(p);Z[++cnth]=z;
      z^=srt[x]^srt[y];
      rcnth++;
      P[cnth]=(Cir){i,Q,z};
      lat[rcnth]=cnth;
    }
    else if(opt[1]=='a'){
      int k;cin>>k;
      P[lat[k]].r=i-1;
    }
    else {
      string p;int k;
      cin>>k>>p;B z(p);
      P[lat[k]].r=i-1;
      P[++cnth]=(Cir){i,Q,Z[lat[k]]^P[lat[k]].w^z};
      Z[cnth]=z;lat[k]=cnth;
    }
  }
  for(int i=1;i<=cnth;i++)
    if(P[i].l<=P[i].r)
      update(1,1,Q,P[i].l,P[i].r,i);
  B ans(0);
  for(int i=1000;i>=0;i--)
    if(exi[i]&&!ans[i])ans^=pf[i];
  print(ans);
  if(Q)solve(1,1,Q);
  return 0;
}

P7230 [COCI 2015/2016 #3] NEKAMELEONI

本题是一类特殊应用,令 \(f_i\) 表示以 \(i\) 为区间左端点右端点至少要取到 \(f_i\),考虑动态维护较为困难,但是动态删除很容易,考虑在位置 \(i\) 删掉一个数(我们让一个位置可以放多个数),则只需要找出前一个与 \(a_i\) 相同的位置 \(L\),后一个 \(R\),然后对于 \(j\in[L+1,i],f_j\leftarrow \max(f_j,i)\) 即可。注意到 \(f_i\) 是单调不降的,所以原本吉司机线段树可以变成区间覆盖,覆盖区间可以线段树上二分出来。然后初始 DP 值用两个 multiset 处理出来即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5,INF=1e9;
int n,k,m,a[N],f[N];
struct his{int p,a,b,c;};
struct Sgt{
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	int tg[N<<2],mn[N<<2],res[N<<2];
	int tp;bool rcd;
	his stk[N*400];
	void pushup(int p){
		if(rcd)stk[++tp]=(his){p,mn[p],res[p],tg[p]};
		mn[p]=min(mn[ls],mn[rs]);
		res[p]=min(res[ls],res[rs]);
	}
	void build(int p,int l,int r){
		if(l==r){mn[p]=f[l];res[p]=f[l]-l+1;return ;}
		build(ls,l,mid);build(rs,mid+1,r);
		pushup(p);
	}
	void mktag(int p,int l,int r,int v){
		if(rcd)stk[++tp]=(his){p,mn[p],res[p],tg[p]};
		mn[p]=v;
		res[p]=v-r+1;
		tg[p]=v;
	}
	void pushdown(int p,int l,int r){
		if(tg[p]){
			mktag(ls,l,mid,tg[p]);
			mktag(rs,mid+1,r,tg[p]);
			if(rcd)stk[++tp]=(his){p,mn[p],res[p],tg[p]};
			tg[p]=0;
		}
	}
	void update(int p,int l,int r,int L,int R,int v){
		if(L<=l&&r<=R){mktag(p,l,r,v);return ;}
		pushdown(p,l,r);
		if(L<=mid)update(ls,l,mid,L,R,v);
		if(R>mid)update(rs,mid+1,r,L,R,v);
		pushup(p);
	}
	int find(int p,int l,int r,int v){
		if(l==r){
			if(mn[p]<v)return l;
			return 0;
		}
		pushdown(p,l,r);
		if(mn[rs]>=v)return find(ls,l,mid,v);
		return find(rs,mid+1,r,v);
	}
	void undo(int x){
		while(tp>x){
			int p=stk[tp].p;
			mn[p]=stk[tp].a;
			res[p]=stk[tp].b;
			tg[p]=stk[tp].c;
			tp--;
		}
	}
}T;
multiset<int>pos[N],num[N];
struct Q{int L,R,p,v;}q[N*2];
int tms,lat[N],cnt;
vector<int>G[N<<2];
void update(int p,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){G[p].push_back(v);return ;}
	if(L<=mid)update(ls,l,mid,L,R,v);
	if(R>mid)update(rs,mid+1,r,L,R,v);
}
void solve(int p,int l,int r){
	int rcd=T.tp;
	for(int i:G[p]){
		int p=q[i].p;
		int v=q[i].v;
		auto it=pos[v].lower_bound(p);
		int L=*prev(it);
		if(L<0)L=0;
		int R=*next(it);
		if(R!=p){
			int fd=T.find(1,1,n,R);
			if(L+1<=fd){
				fd=min(fd,p);
				T.update(1,1,n,L+1,fd,R);
			}
		}
		pos[v].erase(it);
	}
	if(l==r){
		int rp=T.res[1];
		if(rp>n)cout<<'-'<<'1'<<'\n';
		else cout<<rp<<'\n';
		for(int i:G[p]){
			int p=q[i].p;
			int v=q[i].v;
			pos[v].insert(p);
		}
		T.undo(rcd);
		return ;
	}
	solve(ls,l,mid);
	solve(rs,mid+1,r);
	for(int i:G[p]){
		int p=q[i].p;
		int v=q[i].v;
		pos[v].insert(p);
	}
	T.undo(rcd);
}
#undef ls
#undef rs
#undef mid
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>k>>m;
	for(int i=1;i<=k;i++)
		pos[i].insert(-INF),
		pos[i].insert(INF);
	for(int i=1;i<=n;i++){
		cin>>a[i],pos[a[i]].insert(i),
		q[++cnt]=(Q){1,-1,i,a[i]},lat[i]=cnt,
		num[i].insert(a[i]);
	}
	tms=1;
	for(int i=1;i<=m;i++){
		int op;cin>>op;
		if(op==1){
			int p,v;
			cin>>p>>v;
			q[lat[p]].R=tms-1;
			q[++cnt]=(Q){tms,-1,p,v};
			pos[v].insert(p);
			num[p].insert(v);
			lat[p]=cnt;
		}
		else tms++;
	}
	tms--;
	for(int i=1;i<=k;i++)
		f[1]=max(f[1],*pos[i].lower_bound(1));
	for(int i=2;i<=n;i++){
		f[i]=f[i-1];
		for(int j:num[i-1])
			f[i]=max(f[i],*pos[j].lower_bound(i));
	}
	T.build(1,1,n);
	T.rcd=1;
	for(int i=1;i<=cnt;i++){
		if(q[i].R==-1)q[i].R=tms;
		if(q[i].L>1)update(1,1,tms,1,q[i].L-1,i);
		if(q[i].R<tms)update(1,1,tms,q[i].R+1,tms,i);
	}
	solve(1,1,tms);
	return 0;
}
posted @ 2025-05-30 10:29  TBSF_0207  阅读(16)  评论(0)    收藏  举报