首页 > 其他分享 >CF练习题16 (毒瘤数据结构)

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

时间:2023-10-27 20:33:25浏览次数:36  
标签:练习题 return 16 int tr CF mid read inline

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

又是什么奇怪的题目。

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] 序列操作

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

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;
}

标签:练习题,return,16,int,tr,CF,mid,read,inline
From: https://www.cnblogs.com/LiQXing/p/17793100.html

相关文章

  • CF1073G Yet Another LCP Problem
    一道*2600调了一年,代码细节是有点粪了,但自己菜也是挺菜的。/oh/oh考虑容斥,令\(f(A)=\sum\limits_{i,j\inA}\operatorname{lcp}(i,j)\),那么答案就是\(f(A\cupB)-f(A)-f(B)\)(这里的并表示可重集合并)。令\(A=\{a_1,a_2,\cdots,a_m\}\),并且\(a_1\lea_2\le\cdots\lea_m\),......
  • CF248E Piglet's Birthday
    提前了一个月,就做掉了这题,不过还是庆祝一下吧。(考虑dp。令\(f_{u,i}\)表示货架\(u\)还剩\(i\)罐未被吃的蜂蜜的概率。答案就是\(\sumf_{u,0}\)。考虑一次修改\(u\tov\),由于被移动的蜜罐都被吃了,所以\(v\)的\(f\)数组不变,只需要考虑\(f_u\)的变化。枚举吃掉了......
  • CF364D Ghd
    做法大同小异,但不知道为啥我跑这么慢而且还容易被卡。/kk由于这题看上去和概率一点关系都没有并且CF标签中有probabilities,不难想到随机。由于答案子集大小至少为\(n\)的一半,我们每次随机一个数\(a_i\),它在最终答案集合里的概率为\(\frac{1}{2}\)。然后接下来我们考虑硬......
  • #dp,二项式反演,容斥#CF285E Positions in Permutations
    题目问有多少个长度为\(n\)的排列\(P\)满足\(|P_i-i|=1\)的\(i\)的个数恰好为\(k\)个分析设\(dp_{i,j,k}\)表示前\(i\)个数钦定\(j\)个数满足上述条件且现在\(i\)和\(i+1\)因此被占用的方案数。那么第\(i\)个满足上述条件无非就是放入\(i-1\)或者\(......
  • CF1520
    CF1520\(div3\)信心场!DoNotBeDistracted!开一个\(vis\)数组即可只要连续两个字符不相同就将前一个打上标记那么我们访问任意一个具有标记的节点就判断无解即可#include<bits/stdc++.h>usingnamespacestd;#defineendl'\n'#defineinlinline#defineebempl......
  • 数学最终讲义答案8-16章
    格式:练习题所在页-答案所在页。227-465:235-466:248-466:答案466:255-466:255-467:263-467:279-467:286-468:287-468:294-468:309-468:311-469:315-469:319-469:319-469:320-469:326-470:330-470:330-470:334-470:345-470:356-471:366-471:......
  • P4069 SDOI2016 游戏
    传送门solution树剖后一段路径变成了若干链拼起来。自上而下和自下而上分别维护两棵李超线段树,每条链就是一段数轴,提前处理每个点两种方向上的在链内的横坐标。以最近公共祖先为界,把路径分成两段。从一个点向链的顶端跳就是区间加线段。每次跳完要把线段的截距增加一个横坐标偏......
  • CF777题解
    分析先对每一列都做DP寻找极长单调不降区间,能够得到若干极长单调不降区间,只要询问的区间是这些区间的子区间,那么说明在这个区间内必有一列的这个区间是单调不降的。思考如何快速判断子区间。用\(f_{x}\)表示以\(x\)为所有左端点为\(x\)的区间的右端点最大值,那么对于......
  • [CF335F] Buy One,Get One Free
    气死我了,我决定水了这篇题解。反悔贪心,考虑决策及反悔,记到第三层反悔就行。然后你发现要一次只考虑一个不行,要两个两个考虑,然后就做完了,如果深入往下分析能分析出更多可以简化做法的结论。甚至可以简化到只用一层反悔,具体就是第一层可以简化到只记数量,第三层分析出可以归成第二......
  • P5336 [THUSC2016] 成绩单
    这得是区间dp。还需要限制一下值域。因此dp状态时\(f_{l,r,x,y}\)表示使\([l,r]\)区间所有值都处于\([x,y]\)的最小花费。设\(g_{l,r}=\min\{f_{l,r,x,y}+a+b(x-y)^2\}\)。思考一个序列可以被怎么消掉。维护一个类似括号序列的东西,左右匹配的括号......