【2016北京集训】Mushroom

Portal --> broken qwq

Description

  一开始有个蘑菇,蘑菇里面有\(n\)个房间,是一棵有根树,\(1\)号是根,每个房间里面都有杂草,现在要支持以下操作:将某个指定蘑菇复制一份作为一个新的蘑菇;将蘑菇\(v\)合并到蘑菇\(u\)中,有杂草的房间取并(合并后\(v\)不会消失);某个蘑菇子树除草/除子树外除草;某个蘑菇路径除草/除路径外除草;某个蘑菇标号为\(l\sim r\)房间除草/除了这些房间外除草;查询清除某个蘑菇上面所有杂草的时间:一单位时间内可以除最多连续\(w\)个下标连续的房间里面的草

  数据范围:\(n<=50000,q<=100000,w\in[0,200]\)
  

Solution

  首先吐槽一句这什么鬼题啊==突然想丢出题人蘑菇qwq

  首先看那个查询操作,当然是选择贪心啊,每次找到下标最小的有杂草的房间然后往后面跳\(w\)位,再继续找就好了

?  然后。。前面的呢==维护一个二位的数据结构吗qwq

?  实际上正解是bitset,注意到每个房间只有两种状态:有杂草或者没有杂草,所以我们可以用\(0\)和\(1\)来表示这两个状态,然后。。这个时候我们就可以快乐压位并且使用强大无比的位运算了

?  考虑每一个蘑菇开一个bitset,然后复制直接复制,合并就是两个bitset\(\&\)一下,那么其他的各种花式除草怎么搞呢。。注意到花式除草什么的其实我们只要把那一部分的房间状态全部变成\(0\)就好了,然后每个蘑菇内部的树结构是一样的,所以我们可以先预处理出树上每个节点子树内的房间全\(1\)的bitset和树上每个节点到根路径上房间全\(1\)的bitset,然后我们实现一个\(del\)过程:将两个bitset中都为\(1\)的位变为\(0\),那么子树除草直接\(del(subtree)\),子树外除草直接\(\&subtree\),路径除草的两种操作类似,只要先处理出路径上房间全\(1\)的bitset即可(可以用到根路径的bitset亦或起来再强制将\(lca\)那位变为\(1\)),然后至于下标区间修改和查询。。难道暴力跳吗==

  这里就出现了问题,所以我们需要手写bitset,因为自带的bitset并不支持大段大段位往后跳的操作

?  手写bitset的话,实现起来我们可以将\(64\)位压成一个unsigned long long,这样在最后两个与下标相关的操作中,我们就可以像。。分块那样大段大段跳了,写起来。。也和分块有点像(需要注意的是,压位的时候用二进制写出来是二进制的低位是对应排在前面的

?  最后这里mark几个用位运算的操作:
\[
\begin{aligned}
a\% 2^n&=a\&(n-1)\a*2^n&=a<<n\a/2^n&=a>>n
\end{aligned}
\]
  但是。。其实还没有完。。

?  算一下发现空间其实。。有点== 所以这里有一个空间优化的地方就是:因为我们可以离线,所以可以先将那些询问中不需要用到的子树bitset和到根bitset全部delete掉

  

?  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ull unsigned long long
#define Start(x) (x<<6)
#define End(x) (63+(x<<6))
using namespace std;
const int N=50010,TOP=16,B=64,Div=(1<<16)-1;
struct xxx{
    int y,nxt;
}a[N*2];
ull tmprec[N];
int Cnt0[1<<16];
ull lowbit(ull x){return x&-x;}
int CntZero(ull x){//计算写成二进制之后的末尾的0的个数
    if (x<=Div) return Cnt0[x];
    if (x<=(1LL<<32)-1) return 16+Cnt0[x>>16&Div];
    if (x<=(1LL<<48)-1) return 32+Cnt0[x>>32&Div];
    return 48+Cnt0[x>>48&Div];
}
struct bset{/*{{{*/
    ull *a;
    int cntb,cnt1;
    void set(int x){a[x>>6]|=1ULL<<(x&63);}
    void flip(int x){a[x>>6]^=1ULL<<(x&63);}
    void clear(int x){set(x); flip(x);}
    void alloc(int len){
        if (a!=NULL) Clear();
        cntb=(int)ceil(1.0*len/B);
        a=new ull[cntb];
        memset(a,0,sizeof(ull)*cntb);
        int debug=1;
    }
    void Clear(){
        if (a!=NULL){delete a; a=NULL;}
    }
    void operator |= (const bset &x)const{
        for (int i=0;i<cntb;++i) a[i]|=x.a[i];
    }
    void operator ^= (bset x){
        if (x.cntb>cntb) cntb=x.cntb;
        for (int i=0;i<cntb;++i)
            a[i]^=x.a[i];
    }
    void operator &= (const bset &x)const{
        for (int i=0;i<cntb;++i) a[i]&=x.a[i];
    }
    void operator = (bset x){
        if (a==NULL)
            alloc(x.cntb*64);
        for (int i=0;i<cntb;++i) a[i]=x.a[i];
    }
    void del_seg(int l,int r){
        if (l>r) return;
        int L=l>>6,R=r>>6;
        if (L==R)
            for (int i=l;i<=r;++i)
                clear(i);
        else{
            for (int i=L+1;i<R;++i) a[i]=0;
            a[L]&=(1ULL<<(l-Start(L)))-1;
            if (r==End(R))
                a[R]=0;
            else
                a[R]&=~((1ULL<<(r-Start(R))+1)-1);
        }
    }
    void del(bset &x){
        for (int i=0;i<cntb;++i) a[i]^=(a[i]&x.a[i]);
    }
    int query(int w){
        int now=0,ret=0,tmp,st,ed;
        for (int i=0;i<cntb;++i) tmprec[i]=a[i];
        while (now<cntb&&!tmprec[now]) ++now;
        while (1){
            while (now<cntb&&!tmprec[now]) ++now;
            if (now>=cntb) break;
            ++ret;
            st=Start(now)+CntZero(lowbit(tmprec[now]));//有零要跳
            ed=st+w;
            tmp=ed>>6;
            if (ed==End(tmp))
                tmprec[tmp]=0,now=tmp+1;
            else
                tmprec[tmp]&=~((1ULL<<(ed-Start(tmp)+1))-1),now=tmp;
        }
        return ret;
    }
}to_rt[N],subtree[N],rec[N*2];/*}}}*/
struct Q{/*{{{*/
    int op,u,x,y;
    void read(){
        scanf("%d",&op);
        if (op==1) scanf("%d",&u);
        else if (op<=4||op==9) scanf("%d%d",&u,&x);
        else scanf("%d%d%d",&u,&x,&y);
    }
}q[N*2];/*}}}*/
int h[N],f[N][TOP+1],dep[N];
bool use_tort[N],use_subtree[N];
int n,m,tot,id;
//tree part{{{
void add(int x,int y){a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot;}
void dfs(int fa,int x,int d){
    int u;
    to_rt[x]=to_rt[fa]; to_rt[x].set(x-1);
    subtree[x].alloc(n); subtree[x].set(x-1);
    f[x][0]=fa; dep[x]=d;
    for (int i=1;i<=TOP;++i) f[x][i]=f[f[x][i-1]][i-1];
    for (int i=h[x];i!=-1;i=a[i].nxt){
        u=a[i].y;
        if (u==fa) continue;
        dfs(x,u,d+1);
        subtree[x]|=subtree[u];
    }
}
int get_lca(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    for (int i=TOP;i>=0;--i)
        if (dep[f[x][i]]>=dep[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=TOP;i>=0;--i)
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}/*}}}*/

void prework(){
    Cnt0[0]=1;
    for (int i=1;i<16;++i) Cnt0[1<<i]=i;
}
void init(){
    memset(h,-1,sizeof(h));
    tot=0;
}
void presolve(){
    int x,y;
    scanf("%d",&n);
    for (int i=1;i<n;++i){
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    scanf("%d",&m);
    for (int i=1;i<=m;++i){
        q[i].read();
        if (q[i].op<=2) continue;
        if (q[i].op==3||q[i].op==4)
            use_subtree[q[i].x]=true;
        else if (q[i].op<=6)
            use_tort[q[i].x]=true,use_tort[q[i].y]=true;
    }
}
void get_info(){
    to_rt[0].alloc(n);
    dfs(0,1,1);
    for (int i=1;i<=n;++i){
        if (!use_tort[i]) to_rt[i].Clear();
        if (!use_subtree[i]) subtree[i].Clear();
    }
}
void print(ull x){
    for (int i=0;i<64;++i)
        printf("%d",x>>i&1);
}
void debug(bset &x){
    for (int i=0;i<x.cntb;++i)
        print(x.a[i]);
    printf("\n");
}
void solve(){
    id=1;
    rec[id].alloc(n);
    for (int i=1;i<=n;++i) rec[id].set(i-1);
    static bset tmp,tmp1;
    tmp.alloc(n);
    for (int i=1;i<=m;++i){
        if (q[i].op==1) rec[++id]=rec[q[i].u];
        else if (q[i].op==9)
            printf("%d\n",rec[q[i].u].query(q[i].x));
        else{
            tmp=rec[q[i].u];
            if (q[i].op==2)
                tmp|=rec[q[i].x];
            else if (q[i].op==3)
                tmp.del(subtree[q[i].x]);
            else if (q[i].op==4)
                tmp&=subtree[q[i].x];
            else if (q[i].op==7)
                tmp.del_seg(q[i].x-1,q[i].y-1);
            else if (q[i].op==8){
                tmp.del_seg(0,q[i].x-2);
                tmp.del_seg(q[i].y,n-1);
            }
            else{
                //debug(tmp);
                tmp1=to_rt[q[i].x];
                //debug(to_rt[q[i].x]);
                tmp1^=to_rt[q[i].y];
                //debug(to_rt[q[i].y]);
                tmp1.set(get_lca(q[i].x,q[i].y)-1);
                if (q[i].op==5)
                    tmp.del(tmp1);
                else
                    tmp&=tmp1;
                //debug(tmp);
            }
            rec[q[i].u]=tmp;
        }
        //debug(rec[q[i].u]);
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    prework();
    init();
    presolve();
    get_info();
    solve();
}

原文地址:https://www.cnblogs.com/yoyoball/p/9690420.html

时间: 2024-08-30 18:12:18

【2016北京集训】Mushroom的相关文章

【2016北京集训测试赛(八)】 crash的数列

Description 题解 题目说这是一个具有神奇特性的数列!这句话是非常有用的因为我们发现,如果套着这个数列的定义再从原数列引出一个新数列,它居然还是一样的...... 于是我们就想到了能不能用多点数列套着来加速转移呢? 但是发现好像太多数列套起来是可以烦死人的...... 我们就采用嵌套两次吧,记原数列为A,第一层嵌套为B,第二层嵌套为C. 我们其实可以发现一些规律,对于Ci,它对应了B中i的个数:对于Bi,它对应了A中i的个数. 稍加处理即可,我们一边计算一边模拟数列的运算,同时可以计算

【2016北京集训测试赛(八)】直径

注意:时限更改为4s 题解 考虑最原始的直径求法:找到离根节点(或离其他任意一点)最远的节点far1,再从far1出发找到离far1最远的节点far2,far1至far2的距离即为直径. 题目中提到了将原树的子树复制成新子树这一操作,显然如果我们将子树真正复制出来是会爆炸的. 实际上我们可以将每棵新子树中,真正有用的节点提取出来,以简化每个子树的结构,再按照题目的要求连接各个新子树. 我们用虚树来重构每一棵子树.每棵子树的虚树的关键点应至少包含: 子树的根节点. 这棵子树内部的直径的两端节点.

【2016北京集训测试赛(七)】自动机 (思考题)

Time Limit: 1000 ms Memory Limit: 256 MB Description Solution 这是一道看起来令人毫无头绪的题,然而确实十分简单巧妙TAT. 题目要求所有点执行相同指令后都回到初始状态. 我们先来考虑只有两种状态的情况:初始状态$T_0$与另一个状态$T_x$. 这样,我们可以通过一个二元记忆化深搜,来得到一种方案A,使得$T_0$回到$T_0$,且$T_x$回到$T_0$.如果这个方案都不存在,那么此时无解. 现在我们知道,执行方案A后,$T_x$与

【2016北京集训测试赛】river

HINT 注意是全程不能经过两个相同的景点,并且一天的开始和结束不能用同样的交通方式. [吐槽] 嗯..看到这题的想法的话..先想到了每个点的度为2,然后就有点不知所措了 隐隐约约想到了网络流,但并没有继续往下想了... 听完学长的讲评之后(%xj)个人觉得建图还是很有意思的ovo [题解] 因为每个点到对面都有k种方式,那就想到每个点原来的点$x_0$拆成k个点$x_1$, $x_2$, $x_3$... $x_k$ 然后很自然地$x_0$和拆成的点之间要连边 容量的话,因为hint里面的限制

2016北京集训测试赛(十七)- 小结

先说一下总体的情况. 场上期望得分 50 + 40 + 30 = 120 , 最后得分 50 + 0 + 30 = 80 , 实际上自己能力所及能做到的 50 + 65 + 30 = 145 分. 第二题爆零是因为我开始写了一个做法, 后来发现这个做法是错的, 然后开始随机化, 调着调着突然发现只有一分钟了, 然后自己把之前调的改回来, 然后发现怎么全都输出 0 ??? Excuse me ?? 原本不用随机化的做法可以拿 40 分, 如果结合上暴力就有 65 了. 这几天打起比赛来还是暴露了许

【2016北京集训测试赛】azelso(unfinished)

[吐槽] 首先当然是要orzyww啦 以及orzyxq奇妙顺推很强qwq 嗯..怎么说呢虽然说之前零零散散做了一些概d的题目但是总感觉好像并没有弄得比较明白啊..(我的妈果然蒟蒻) 这题的话可以说是难得的一道搞得比较清楚的概d题目吧记录一下还是挺有意思的ovo 当然咯..显然考场上并没有推出来..嗯qwq [题解] 看到说要求期望的距离,然后总的长度又被分成了一段一段的(各个事件) 所以就有一个比较直接的想法:将每一段期望走的次数算出来然后再乘上每一段的距离,加起来就是答案啦 那么现在问题来了怎

[2016北京集训测试赛(一)]奇怪的树-[树链剖分]

Description Solution 对于操作1,不论选了哪个点为a,最后反转颜色的点集都只有两种类型(显然啦). 暴力解法:对每个操作3,从a向上直到根节点,每到一个节点记录(它父亲的黑点数减去自己的黑点数)*父亲节点的编号.另外,还要记录a子树内的黑点.. 这种O(n2)的做法肯定会爆,考虑优化.由于这是一棵静态树,考虑树链剖分. 需要记录一个数组re[x][0/1][0/1].第2维表示深度的奇偶,第3维表示点的颜色.例如:re[x][0][0]记录的是初始情况下以x为根的子树中深度为

[2016北京集训测试赛(五)]打地鼠-[思考题]

Description Solution 我们先只考虑一只地鼠的情况,依题意得,在某一个时刻该地鼠的可能停留位置是一个公差为2的等差数列.我们设这个等差数列的两端为[L,R].则如果区间[L+1,R-1]的格子被打实际上是不会影响L和R的(列一个等差数列实际模拟一下就发现啦).而如果格子L被打,则L+2:如果格子R被打,则R-2.打了格子后,别忘了L--,R++. 嗯根据以上性质,我们可以知道,地鼠1,3,5,7,9...的L是非递减的,地鼠2,4,6,8,10...的L也是非递减的. 然后看一

[2016北京集训试题6]网络战争-[最小割树(网络流)+kd-tree+倍增]

Description A 联邦国有 N 个州,每个州内部都有一个网络系统,有若干条网络线路,连接各个 州内部的城市. 由于 A 国的州与州之间的关系不是太好,每个州都只有首府建立了到别的州的网络.具体来说,每个州的首府都只主动地建立了一条网络线路,连接到距离最近的州的 首府.(欧氏距离.如果有多个,选择标号最小的去连接) B 国探知了 A 国的网络线路分布情况,以及攻陷每条网络线路所需花费的代价,B 国首脑想知道断开 A 国某两个城市之间的网络连接,所需的最少代价.请你计算出来告 诉他. 注: