Loading

NOIP 模拟 6 模板

题目

题解

这道题是一道启发式合并的题目,每次合并完重构一下线段树就可以,不用线段树合并。

以操作时间为下标,建立一颗线段树,维护小球的个数与小球的颜色数,最后线段树上二分查找。

我们先不用考虑每个节点放小球数的限制,最后二分查找时,找小球数 \(\leq\) 限制数的所对应的节点下标的颜色数。

在本题中,我们要用一个 \(vector\) 来存储,通过 \(vector\) 的动态内存释放来防止 \(MLE\)

而且也要注意,在本题中颜色编号可能为负数,所以我们要先将其映射到正整数范围内,这个可以用 \(map\)

还有一个挺玄学的地方就是启发式合并,通俗得说就是由一个小集合向一个大集合合并,这样可以减少合并次数,从而减小复杂度。

这体现到本题上就是对于一个节点,如果其非叶节点,找到一个操作数最多的子树,将其它子树向这个重儿子合并。

细节挺多,且卡常的地方也很多,具体看代码

\(AC\kern 0.5emCODE:\)

Code
#include<bits/stdc++.h>
#define ri register int
#define p(i) ++i
using namespace std;
namespace IO{
    char buf[1<<21],*p1=buf,*p2=buf;
    #define gc() p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++
    inline int read() {
        ri x=0,f=1;char ch=gc();
        while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
        while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
        return x*f; 
    }
}
using IO::read;//快读
namespace nanfeng{
    #define cmax(x,y) ((x)>(y)?(x):(y))
    #define cmin(x,y) ((x)>(y)?(y):(x))
    #define FI FILE *IN
    #define FO FILE *OUT
    #define pb(x) push_back(x)
    #define node(c,id) (node){c,id} 
    const int N=1e5+7;
    struct edge{int v,nxt;}e[N<<1];
    struct node{int c,id;};
    int first[N],buc[N],tms[N],son[N],ans[N],ocu[N],t=1,tot,n,m,Q;
    map<int,int> col;//可能会有负数
    vector<node> ch[N];//二位数组会mle,node 可以用pair替换
    inline void add(int u,int v) {
        e[t].v=v;
        e[t].nxt=first[u];
        first[u]=t++;
    } 
    struct Seg{//封装
        #define ls(x) (x<<1)
        #define rs(x) (x<<1|1)
        struct Segmenttree{int sz,szc,lz;}T[N<<2];
        inline void init(int x) {
            T[1].sz=T[1].sz=0;
            T[1].lz=1;//懒标记实在初始化时用,类似与区间更新,没用到的区间就不初始化
            for (ri i(0);i<ch[x].size();p(i)) ocu[ch[x][i].c]=0;
        }
        inline void down(int x) {
            if (!T[x].lz) return;
            int l=ls(x),r=rs(x);
            T[x].lz=T[l].sz=T[l].szc=T[r].sz=T[r].szc=0;
            T[l].lz=T[r].lz=1;
        }
        inline void up(int x) {
            int l=ls(x),r=rs(x);
            T[x].sz=T[l].sz+T[r].sz;
            T[x].szc=T[l].szc+T[r].szc;
        }
        void update(int x,int l,int r,int k,int c,int nm) {
            if (l==r) {T[x].sz+=nm;T[x].szc+=c;return;}
            down(x);
            int mid=(l+r)>>1;
            if (k<=mid) update(ls(x),l,mid,k,c,nm);
            else update(rs(x),mid+1,r,k,c,nm);
            up(x);
        }
        inline void merge(int x) {
            for (ri i(0);i<ch[x].size();p(i)) {
                node tmp=ch[x][i];
                if (!ocu[tmp.c]) {
                    update(1,1,m,tmp.id,1,1);
                    ocu[tmp.c]=tmp.id;
                } else if (ocu[tmp.c]>tmp.id) {
                    update(1,1,m,ocu[tmp.c],-1,0);
                    update(1,1,m,tmp.id,1,1);
                    ocu[tmp.c]=tmp.id;
                } else update(1,1,m,tmp.id,0,1);
            }
        }
        int query(int x,int l,int r,int k) {
            if (!k) return 0;
            if (l==r) return T[x].szc;
            int mid=(l+r)>>1;
            down(x);
            int lt=ls(x),rt=rs(x);
            if (T[lt].sz<=k) return T[lt].szc+query(rt,mid+1,r,k-T[lt].sz);//以小球数二分
            else return query(lt,l,mid,k);
        }
    }T;
    void dfs_init(int x,int fa) {
        tms[x]=ch[x].size();
        for (ri i(first[x]),v;i;i=e[i].nxt) {
            if ((v=e[i].v)==fa) continue;
            dfs_init(v,x);
            tms[x]+=tms[v];
            if (tms[v]>tms[son[x]]) son[x]=v;//这里有一个优化,因为是启发式合并,所以要先处理出操作数多的重儿子,由小向大合并,减少合并数
        }
    }
    void dfs(int x,int fa) {
        for (ri i(first[x]),v;i;i=e[i].nxt) {
            if ((v=e[i].v)==fa||v==son[x]) continue;
            dfs(v,x);T.init(v);//记得回溯
        }
        if (son[x]) dfs(son[x],x);//重儿子单独搜,不用回溯
        T.merge(x);
        for (ri i(first[x]),v;i;i=e[i].nxt) {
            if ((v=e[i].v)==fa||v==son[x]) continue;
            T.merge(v);
        }
        ans[x]=T.query(1,1,m,buc[x]);//二分查找
        if (son[x]) {
            int sx=son[x];
            for (ri i(0);i<ch[x].size();p(i)) ch[sx].pb(ch[x][i]);
            swap(ch[x],ch[sx]);//启发式合并
            for (ri i(first[x]),v;i;i=e[i].nxt) {
                if ((v=e[i].v)==fa||v==son[x]) continue;
                for (ri i(0);i<ch[v].size();p(i)) ch[x].pb(ch[v][i]);
            }
        }
    }
    inline int main() {
        // FI=freopen("nanfeng.in","r",stdin);
        // FO=freopen("nanfeng.out","w",stdout);
        n=read();
        for (ri i(1);i<n;p(i)) {
            int u=read(),v=read();
            add(u,v);add(v,u);
        }
        for (ri i(1);i<=n;p(i)) buc[i]=read();
        m=read();
        for (ri i(1);i<=m;p(i)) {
            int x=read(),c=read();
            if (!col[c]) col[c]=p(tot),c=tot;//映射成整数范围
            else c=col[c];
            ch[x].pb(node(c,i));
        }
        dfs_init(1,0);dfs(1,0);
        Q=read();
        for (ri i(1),x;i<=Q;p(i)) printf("%d\n",ans[x=read()]); 
        return 0;
    }
}
int main() {return nanfeng::main();}

时间复杂度为 \(\mathcal O(nlog^2n)\)?我不太会分析,哪位大佬教我一下?

posted @ 2021-06-11 19:29  ナンカエデ  阅读(43)  评论(1编辑  收藏  举报