CF练习题16 (毒瘤数据结构)

Lomsat gelral

把树拍成一条链,询问子树等于询问区间。

这样一看这道题就非常莫队。

但是有要求个数最多的颜色的编号,我们可以用线段树动态维护颜色的最大值。

这样,一个无脑莫队线段树的暴力就做出来了。

int n,a[N];
int dfn[N],nfd[N],cnt;
int b[N],siz[N];
vector<int>g[N];
inline void dfs(int u,int from){
    dfn[u]=++cnt;nfd[cnt]=u;
    b[cnt]=a[u];
    siz[u]=1;
    for(auto v:g[u]){
        if(v==from)continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
}
struct ask{
    int l,r,id;
}q[N];
int pos[N],blo;
inline bool cmp(ask x,ask y){
	if(pos[x.l]!=pos[y.l])return pos[x.l]<pos[y.l];
	if(pos[x.l]&1)return x.r>y.r;
	return x.r<y.r;
}
int ans[N];
int coln[N];
struct SegmentTree{
    struct node{
        int l,r,maxl,maxpos;
    }tr[N<<2];
    inline void push_up(int k){
        tr[k].maxl=max(tr[lc].maxl,tr[rc].maxl);
        if(tr[k].maxl==tr[lc].maxl)tr[k].maxpos+=tr[lc].maxpos;
        if(tr[k].maxl==tr[rc].maxl)tr[k].maxpos+=tr[rc].maxpos;
    }
    inline void build(int k,int l,int r){
        tr[k].l=l;tr[k].r=r;
        if(l==r){
            tr[k].maxl=0;
            tr[k].maxpos=l;
            return;
        }
        int mid=(l+r)>>1;
        build(lc,l,mid);
        build(rc,mid+1,r);
        push_up(k);
    }
    inline void add(int k,int l,int r,int val){
        if(tr[k].l>=l&&tr[k].r<=r){
            tr[k].maxl+=val;
            return;
        }
        int mid=(tr[k].l+tr[k].r)>>1;
        if(mid>=l)add(lc,l,r,val);
        if(mid<r)add(rc,l,r,val);
        push_up(k);
    }
}T;
inline void add(int x){
    T.add(1,x,x,1);
}
inline void del(int x){
    T.add(1,x,x,-1);
}
signed main(){
	n=read();
    blo=sqrt(n);
    up(i,1,n){
        a[i]=read();
        pos[i]=(i-1)/blo+1;
    } 
    int u,v;
    up(i,1,n-1){
        u=read();v=read();
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1,0);
    up(i,1,n){
        q[i].l=dfn[i];
        q[i].r=dfn[i]+siz[i]-1;
        q[i].id=i;
    }
    T.build(1,1,n);
    sort(q+1,q+1+n,cmp);
    int L=1,R=0;
    up(i,1,n){
        while(R<q[i].r)add(b[++R]);
        while(R>q[i].r)del(b[R--]);
        while(L>q[i].l)add(b[--L]);
        while(L<q[i].l)del(b[L++]);
        ans[q[i].id]=T.tr[1].maxpos;
        
    }
    up(i,1,n)write(ans[i],0);
    return 0;
}

当然,这道题正解应该是 \(dsu\ on\ tree\)

用树上启发式合并又该怎么做呢?

我们先考虑暴力,对每一个点搜索其子树,暴力查询自树中颜色最多的颜色。

这个的复杂度是 \(O(n^2)\) 的。

但是我们发现,对于每个节点 \(v\),最后一棵子树是不用清空的,因为做完那棵子树后可以把其结果直接加入 \(v\) 的答案中。

那么我们选择那颗子树为 \(v\) 的重儿子。

看起来没有什么优化,实际上复杂度为 \(O(n\log n)\)

代码我就不放了。

Frogs and mosquitoes

恶心题。

很暴力的,你可以对每一个蚊子,向前枚举最远的青蛙,这个青蛙吃到了这只蚊子,舌头变长,判断能否吃到蚊子,如果不能,加入新的蚊子。

当然,这个暴力是 \(n^2\) 的,肯定过不了。

考虑优化。

在向前枚举青蛙这一步时,我们可不可以二分,似乎是不行的。因为有青蛙的是不满足单调性的,所以我们可以预处理一下青蛙,把所有区间会被前面的青蛙完全覆盖的青蛙去掉,这样,青蛙就可以满足单调性。

所以可以二分找到最左边的青蛙。

找到那只青蛙,把舌头伸长。这时就可以对蚊子在的一个集合进行二分。找出能够吃那些蚊子。

最后记得继续删除青蛙来满足数组的单调性。

复杂度 \(O(n\log n)\)

输入图片说明

输入图片说明

int n,m;
struct frog{
    int x,t,id;
}a[N];
int ansl[N],ansc[N];
inline bool cmp(frog x,frog y){
    if(x.x==y.x)return x.t<y.t;
    return x.x<y.x;
}
int maxl=0;
vector<frog>t;
int k=0;
inline int ask(int p){
    int l=1,r=k,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(t[mid].x+t[mid].t>=p){
            r=mid-1;
            ans=mid;
        }
        else l=mid+1;
    }
    if(t[ans].x<=p&&t[ans].x+t[ans].t>=p) return ans;
    return 0;
}
multiset<pii>s;
inline void work(int x){
	while(x<k){
		if(t[x+1].x+t[x+1].t<=t[x].x+t[x].t) t.erase(t.begin()+x+1),k--;
		else break;
	}
}
signed main(){
	n=read();m=read();
    up(i,1,n){
        a[i].x=read();
        a[i].t=read();
        a[i].id=i;
        ansl[i]=a[i].t;
    }
    sort(a+1,a+1+n,cmp);
    t.push_back({0,0,0});
    up(i,1,n){
        if(a[i].x+a[i].t>maxl){
			t.push_back(a[i]);
            k++;
			maxl=a[i].x+a[i].t;
		}
    }
    int x,delt;
    up(i,1,m){
        x=read();delt=read();
        int p=ask(x);
        if(p==0){
            s.insert({x,delt});
            continue;
        }
        ansl[t[p].id]+=delt;
		ansc[t[p].id]++;
        t[p].t+=delt;
		while(!s.empty()){
            auto it=s.lower_bound({t[p].x,0});
            if((it==s.end())||(t[p].x+t[p].t<(it->fi))) break;
            ansl[t[p].id]+=(it->se);
            t[p].t+=(it->se);
            ansc[t[p].id]++; 
            s.erase(it);
        }
        work(p);
    }
    up(i,1,n){
        cout<<ansc[i]<<" "<<ansl[i]<<endl;
    }
    return 0;
}

New Year Tree

二进制线段树。

Zbazi in Zeydabad

又是什么奇怪的题目。

题解的 bitset 能艹过去,但是我的 bitset 会 Tle,真是见了鬼。

还是老老实实写树状数组吧。

观察数据范围,应该有一个 \(n^2\) 的做法。

枚举右上角的拐点 \((i,j)\) ,这个点向左最多 \(l\)\(1\),向斜下有 \(d\)\(1\) 有那么所有满足情况底边 \(x\)

\(i\le x\le i+\min(l,d)\),同时 \(x-l_{x,j}+1\le i\)

啊这,直接上二维数点就行了。

char c[3005][3005];
int l[3005][3005],ld[3005][3005];
struct BIT {
	int t[3005];
	int lb(int x) {return x&-x;}
	int sum(int x) {
	    int s=0;
		for(int i=x;i;i-=lb(i)) 
            s+=t[i];    
	    return s;
	}
	int query(int x,int y) {
		return sum(y)-sum(x-1);
	}
	void add(int x,int k) {
		for(int i=x;i<=n;i+=lb(i)) 
            t[i]+=k;
	}
} t[3005];
struct node {int l,r,x,y,p;} a[9000005],q[9000005];
int cnt1,cnt2;
bool cmp(node a1,node a2) {return a1.p<a2.p;}
signed main() {
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			cin>>c[i][j];
			if(c[i][j]=='z')
				l[i][j]=l[i][j-1]+1;
		}
	for(int i=n;i>=1;i--)
		for(int j=1;j<=m;j++)
			if(c[i][j]=='z')
				ld[i][j]=ld[i+1][j-1]+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			a[++cnt1]={0,0,i,j,i-l[i][j]+1};
			q[++cnt2]={i,i+min(l[i][j],ld[i][j])-1,i,j,i};
		}
	sort(a+1,a+cnt1+1,cmp);
	sort(q+1,q+cnt2+1,cmp);
	for(int i=1,j=1;j<=cnt2;j++) {
		while(a[i].p<=q[j].p&&i<=cnt1)
			t[a[i].y].add(a[i].x,1),i++;
		ans+=t[q[j].y].query(q[j].l,q[j].r);
	}
	cout<<ans;
	return 0;
}

Lena and Queries

看起来似乎是进行一个李超树的操作,但是里面有操作删除,所以考虑线段树分治套李超树。

考虑求出一个线段的存在时间。然后在时间轴上建出一棵线段树,然后对线段树上每个要求的节点建立一棵动态开点李超线段树。

int n;
int pos[N];
inline void build(int k,int l,int r){
    if(l==r){
        pos[l]=k;
        return;
    } 
    int mid=(l+r)>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
}
int rt[N<<2];
struct node{
    int ls,rs,id;
}tr[N<<5];
struct ask{
    int op,l,r,k,b;
}q[N];
int tot;
inline int clac(int i,int x){return q[i].k*x+q[i].b;}
inline void insert(int &x,int l,int r,int p){
    if(!x) x=++tot;
    if(!tr[x].id){
        tr[x].id=p;
        return;
    } 
    int mid=(l+r)>>1;
    if(clac(p,mid)>clac(tr[x].id,mid))swap(p,tr[x].id);
    if(clac(p,l)>clac(tr[x].id,l))insert(tr[x].ls,l,mid,p);
    if(clac(p,r)>clac(tr[x].id,r))insert(tr[x].rs,mid+1,r,p);
}
inline int ask(int k,int l,int r,int p){
    if(!k) return LLONG_MIN;
    if(l==r)return clac(tr[k].id,p);
    int mid=(l+r)>>1;
    return max(p<=mid?ask(tr[k].ls,l,mid,p):ask(tr[k].rs,mid+1,r,p),clac(tr[k].id,p));
}
inline void add(int k,int l,int r,int ql,int qr,int val){
    if(l>=ql&&r<=qr){
        insert(rt[k],-1e9,1e9,val);
        return;
    } 
    int mid=(l+r)>>1;
    if(ql<=mid)add(lc,l,mid,ql,qr,val);
    if(mid<qr)add(rc,mid+1,r,ql,qr,val);
}
signed main(){
	n=read();
    build(1,1,n);
    up(i,1,n){
        q[i].op=read();
        if(q[i].op==1)q[i].k=read(),q[i].b=read(),q[i].l=i,q[i].r=n;
        else if(q[i].op==2)q[read()].r=i;
        else q[i].k=read();
    }
    up(i,1,n){
        if(q[i].op==1)add(1,1,n,q[i].l,q[i].r,i);
    }
    up(i,1,n){
        if(q[i].op==3){
            int ans=LLONG_MIN;
            for(int x=pos[i];x;x>>=1)ans=max(ans,ask(rt[x],-1e9,1e9,q[i].k));
            if(ans==-LLONG_MIN)puts("EMPTY SET");
            else write(ans,1);
        }
    }
    return 0;
}

Radio stations

就套上一个三维偏序的板子就行了。

int n,k;
struct node{
    int x,r,f,L,R;
}s[N];
vector<int>h;
struct Bit{
    inline int lowbit(int x){
        return x&(-x);
    }
    int tr[N];
    inline void add(int x,int val){
        for(;x<h.size()+100;x+=lowbit(x))tr[x]+=val;
    }
    inline int ask(int x){
        int res=0;
        for(;x;x-=lowbit(x))res+=tr[x];
        return res;
    }
    inline void clear(){
        memset(tr,0,sizeof tr);
    }
}T;

inline int id(int x){
    return lower_bound(h.begin(),h.end(),x)-h.begin()+2;
}
inline bool cmpr(node a,node b) {
	if(a.r==b.r) return a.f<b.f||a.f==b.f&&a.x<b.x;
	return a.r>b.r;
}
inline bool cmpf(node a,node b) {
	if(a.f==b.f)return a.r>b.r||a.r==b.r&&a.x<b.x;
	return a.f<b.f;
}
int ans;
inline void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    sort(s+l,s+mid+1,cmpf);
    sort(s+mid+1,s+r+1,cmpf);
    T.clear();
    int fmin=l,fmax=l,u=mid+1;
	while(u<=r){
		while(s[u].f-s[fmin].f>k&&fmin<=mid)T.add(s[fmin++].x,-1);
		while(s[fmax].f-s[u].f<=k&&fmax<=mid)T.add(s[fmax++].x,1);
		ans+=T.ask(s[u].R)-T.ask(s[u].L-1);
        u++;
	}
}
signed main(){
    n=read();k=read();
    up(i,1,n){
        s[i].x=read();
        s[i].r=read();
        s[i].f=read();
        s[i].L=max(1,s[i].x-s[i].r);
		s[i].R=s[i].x+s[i].r;
        h.push_back(s[i].R);
        h.push_back(s[i].L);
        h.push_back(s[i].x);
    }
    sort(h.begin(),h.end());
    h.erase(unique(h.begin(),h.end()),h.end());
    up(i,1,n){
        s[i].x=id(s[i].x);
        s[i].L=id(s[i].L);
        s[i].R=id(s[i].R);
    }
    sort(s+1,s+1+n,cmpr);
    cdq(1,n);
    cout<<ans;
    return 0;
}

Periodic RMQ Problem

考虑暴力动态开点。

注意进行区间覆盖时,左右的区间都要新建,不然 \(push\_up\) 会有问题。

struct Node{int w,ch[2];}a[N<<6];
int f[20][N],pre[N],suc[N],minn,n,nn,q,lg[N],node_cnt,tag[N<<6],rt;
int st_query(int l,int r){
	int len=lg[r-l+1];
	return min(f[len][l],f[len][r-(1<<len)+1]);
}
void make(){
	lg[1]=0;for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;(1<<i)<=n;i++)
		for(int j=1;j+(1<<i)-1<=n;j++)
			f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
	pre[1]=f[0][1];for(int i=2;i<=n;i++)pre[i]=min(pre[i-1],f[0][i]);
	suc[n]=f[0][n];for(int i=n-1;i>=1;i--)suc[i]=min(suc[i+1],f[0][i]);
	minn=pre[n];
}
int query(int l,int r){
	if(r-l+1>=n)return minn;
	int bl=(l-1)/n+1,br=(r-1)/n+1;
	if(bl==br){int L=(bl-1)*n;return st_query(l-L,r-L);}
	else{int L=(bl-1)*n,R=(br-1)*n;return min(suc[l-L],pre[r-R]);}
}
void newnode(int &rot,int lt,int rt){
	rot=++node_cnt;
	a[rot].w=query(lt,rt);
}
void upd(int rot,int w){a[rot].w=tag[rot]=w;}
void pushdown(int rot){
	if(tag[rot]){
		int t=tag[rot];tag[rot]=0;
		if(!a[rot].ch[0])a[rot].ch[0]=++node_cnt;
		if(!a[rot].ch[1])a[rot].ch[1]=++node_cnt;
		upd(a[rot].ch[0],t),upd(a[rot].ch[1],t);
	}
}
void update(int &rot,int lt,int rt,int lq,int rq,int w){
	if(!rot)newnode(rot,lt,rt);
	if(lt>rq||rt<lq)return;
//	cout<<lt<<" "<<rt<<" "<<a[rot].w<<endl;
	if(lt>=lq&&rt<=rq){upd(rot,w);return;}
	pushdown(rot);int mid=(lt+rt)>>1;
	update(a[rot].ch[0],lt,mid,lq,rq,w),update(a[rot].ch[1],mid+1,rt,lq,rq,w);
	a[rot].w=min(a[a[rot].ch[0]].w,a[a[rot].ch[1]].w);
}
int query(int &rot,int lt,int rt,int lq,int rq){
	if(!rot)newnode(rot,lt,rt);
//	cout<<lt<<" "<<rt<<" "<<a[rot].w<<endl;
	if(lt>=lq&&rt<=rq)return a[rot].w;
	int mid=(lt+rt)>>1;pushdown(rot);
	if(rq<=mid)return query(a[rot].ch[0],lt,mid,lq,rq);
	else if(lq>mid)return query(a[rot].ch[1],mid+1,rt,lq,rq);
	else return min(query(a[rot].ch[0],lt,mid,lq,mid),query(a[rot].ch[1],mid+1,rt,mid+1,rq));
}
int getin(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
	return x;
}
int main(){
	n=getin(),nn=getin()*n;
	for(int i=1;i<=n;i++)f[0][i]=getin();
	make();
	q=getin();a[0].w=2e9;
	for(int i=1;i<=q;i++)
	{
		int opt=getin(),l=getin(),r=getin(),x;
		if(opt==1)x=getin(),update(rt,1,nn,l,r,x);
		else printf("%d\n",query(rt,1,nn,l,r));
	}
}

MEX Queries

我们需要维护三种操作。

  1. [l,r]赋值为 \(1\)
  2. [l,r]赋值为 \(0\)
  3. [l,r]所有的数取反。
    询问出现的第一个 \(0\) 的位置。

看起来就非常的珂朵莉。

struct node{
    int l,r;
    mutable int v;
    inline bool operator<(const node&rhs)const{
        return l<rhs.l;
    }
};
set<node>odt;
inline auto split(int pos){
    auto it=odt.lower_bound({pos,0,0});
    if(it!=odt.end()&&it->l==pos)return it;
    it--;
    int l=it->l,r=it->r,v=it->v;
    odt.erase(it);
    odt.insert({l,pos-1,v});
    return odt.insert({pos,r,v}).fi;
}
inline void assign(int l,int r,int v){
    auto ed=split(r+1),bg=split(l);
	odt.erase(bg,ed);
	odt.insert({l,r,v});
}
inline void rev(int l,int r){
    auto ed=split(r+1);
    for(auto it=split(l);it!=ed;it++)it->v^=1;
}
inline void ask(){
    for(auto it=odt.begin();it!=odt.end();it++){
        if(it->v==0){
            write(it->l,1);
            break;
        }
    }
}
int n;
signed main(){
    odt.insert({1,uinf,0});
    n=read();
    int op,l,r;
    up(i,1,n){
        op=read();
        if(op==1){
            l=read();r=read();
            assign(l,r,1);
        }
        else if(op==2){
            l=read();r=read();
            assign(l,r,0);
        }
        else{
            l=read();r=read();
            rev(l,r);
        }
        ask();
    }
    return 0;
}

当然,如果不用珂朵莉树,这道题也可以做,前面三种操作可以看一下这道题:P2572 [SCOI2010] 序列操作

把他动态开点一下就是了。

Functions On The Segments

可持久化线段树套李超线段树,最后查询就是版本相减。

无他,为码量尔。

int n,m;
int rt[N];
struct node{
    int ls,rs;
    int suma,sumb;
}tr[N<<5];
int cnt;
inline void change(int &k,int pre,int l,int r,int pos,int vala,int valb){
    k=++cnt;
    tr[k]=tr[pre];
    tr[k].suma+=vala;
    tr[k].sumb+=valb;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=pos)change(tr[k].ls,tr[pre].ls,l,mid,pos,vala,valb);
    else change(tr[k].rs,tr[pre].rs,mid+1,r,pos,vala,valb);
}
inline pii operator +(const pii x,const pii y){
    return {x.fi+y.fi,x.se+y.se};
}
inline pii operator -(const pii x,const pii y){
    return {x.fi-y.fi,x.se-y.se};
}
inline pii ask(int k,int l,int r,int ql,int qr){
    if(l>=ql&&r<=qr)return {tr[k].suma,tr[k].sumb};
    int mid=(l+r)>>1;
    pii ans={0,0};
    if(mid>=ql)ans=ans+ask(tr[k].ls,l,mid,ql,qr);
    if(mid<qr)ans=ans+ask(tr[k].rs,mid+1,r,ql,qr);
    return ans;
}
signed main(){
    n=read();
    int x1,x2,y1,y2,a,b;
    up(i,1,n){
        x1=read();x2=read();y1=read();
        a=read();b=read();y2=read();
        int rt1,rt2;
        change(rt1,rt[i-1],0,mod,0,0,y1);
        change(rt2,rt1,0,mod,x1+1,a,b-y1);
        change(rt[i],rt2,0,mod,x2+1,-a,y2-b);
    }
    m=read();
    int last=0,l,r,k;
    pii ans;
    up(i,1,m){
        ans={0,0};
        l=read();r=read();k=(read()+last)%mod;
        ans=ans+ask(rt[r],0,mod,0,k);
        if(l)ans=ans-ask(rt[l-1],0,mod,0,k);
        last=ans.fi*k+ans.se;
        write(last,1);
    }
    return 0;
}

Turn Off The TV

线段树区间加,区间最小值,需要动态开点。


struct node{
	int ls,rs,minl,tag;
}tr[N<<5];
int n,m,cnt=1;//一定要赋值成1
inline void push_up(int k){
	tr[k].minl=min(tr[tr[k].ls].minl,tr[tr[k].rs].minl);
}
inline void push_down(int k,int l,int r){
    if(tr[k].tag){
        if(!tr[k].ls)tr[k].ls=++cnt;
        if(!tr[k].rs)tr[k].rs=++cnt;
        tr[tr[k].ls].tag+=tr[k].tag;
       	tr[tr[k].rs].tag+=tr[k].tag;
        int mid=(l+r)>>1;
        tr[tr[k].ls].minl+=tr[k].tag;
        tr[tr[k].rs].minl+=tr[k].tag;
        tr[k].tag=0;
    }
}
inline void change(int &k,int l,int r,int x,int y,int val){
	if(!k)k=++cnt;
	if(x<=l&&r<=y) {
        tr[k].tag+=val;
		tr[k].minl+=val;
        return;
    }
    int mid=(l+r)>>1;
    push_down(k,l,r);
    if(x<=mid)change(tr[k].ls,l,mid,x,y,val);
    if(y>mid)change(tr[k].rs,mid+1,r,x,y,val);
    push_up(k);
}
inline int ask(int k,int l,int r,int x,int y){
    if(!k)return 0;
    if(x<=l&&y>=r)return tr[k].minl;
    push_down(k,l,r);
    int mid=(l+r)>>1,res=inf;
    if(x<=mid)res=min(res,ask(tr[k].ls,l,mid,x,y));
    if(y>mid)res=min(res,ask(tr[k].rs,mid+1,r,x,y));
    return res;
}
int L[N],R[N];
signed main(){
	n=read();
    int l,r;
    up(i,1,n){
        L[i]=read();R[i]=read();
        int tmp=1;
        change(tmp,0,mod,L[i],R[i],1);
    }
    up(i,1,n){
        int tmp=1;
        int t=ask(tmp,0,mod,L[i],R[i]);
        if(t>=2){
            cout<<i;
            return 0;
        }
    }
    cout<<-1;
    return 0;
}
posted @ 2023-10-27 20:25  LiQXing  阅读(33)  评论(0)    收藏  举报