zoukankan      html  css  js  c++  java
  • P5025 [SNOI2017]炸弹 题解

    蒟蒻的第一篇黑题题解(学了这么长时间了才第一道也是没谁了。)

    题目链接

    Solution

    朴素:

     根据题目描述,我们可以处理每一个x节点左右爆炸半径范围内的点,然后模拟一次爆炸 (for),遍历每一个点。每当我们遍历到一个点,我们就对这个点在进行一次处理半径+dfs直到没有能遍历的了,直接统计遍历的点数然后输出。

    时间复杂度:O(n^2)级别。

    于是乎:

      

     是不是可以暴力踩标算了呢?

    显然完全接受不了。

    因为这个算法要考虑到连边操作。对于每一个点,我们要将它自己连向能够遍历到的边,那么意味着,一个边要被连很多次。

    就如下图:

     其中编号为1的节点能炸到1 2 3 4 5,2能炸到1 2 3,3能炸到1 2 3 4 5,其中2 3 4挺惨的。

     于是有了这么一个线段树建图的思路:

    考虑线段树的性质,上层节点可以代表下层节点的一段区间,那么我们是不是也可以建这类的上层边,表示通过这条边能遍历到它对应的下层边的所有点(就是这句精华)

    这样我们先把所有点进行从1到n的编号,然后跑线段树的build建边,然后依次对每一个点进行从底层(只能这么理解)到上层节点的连边表示能遍历到上层节点能遍历到的节点。

    这样的话,会形成很多环。在对每一个点都进行范围连边以后,对于一个环(或者强连通分量)内的点,自然能互相遍历到,为了方便统计,我们需要跑一个tarjan记录强连通分量内的点能遍历到的左右区间范围。

    跑完tarjan就可以O(n)遍历一遍按照题目所说进行统计了呢。

    部分代码

    build:

    void build(int now,int l,int r)
    {
        canl[now]=l;canr[now]=r;//can表示当前节点能遍历到的左右区间范围 
        if(l==r)//线段树日常操作 
        {
            id[r]=now;
            return;
        }
        int mid=(l+r)>>1;
        build(now<<1,l,mid);build(now<<1|1,mid+1,r);
        add(now,now<<1);add(now,now<<1|1);//不同于线段树的地方,向左右两个节点连边,往下层伸展 
    }

    下层往上连边部分:

    void lb(int mb,int l,int r,int L,int R,int now)
    {
        if(l>=L&&r<=R)
        {
            if(mb==now)//mb表示目标节点,也就是当前初始节点 
                return;
            add(mb,now);//now表示线段树上当前节点,从mb到now连边表示mb这个节点可以爆炸到now节点代表的区间 
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)lb(mb,l,mid,L,R,now<<1);//左右区间连边 
        if(R>mid)lb(mb,mid+1,r,L,R,now<<1|1);
    }

    tarjan部分:

    void tarjan(int now)
    {
        dfn[now]=++tarjannum;//dfn都知道是什么吧 
        low[now]=tarjannum;
        vis[now]=1;//设置入栈 
        st[++top]=now;
        for(int i=hea[now];i;i=edge[i].nex)
        {
            int v=edge[i].to;
            if(!dfn[v])
            {
                tarjan(v);low[now]=min(low[now],low[v]);
            }
            else
                if(vis[v]) //在栈中 
                    low[now]=min(low[now],dfn[v]);//以上正常tarjan操作 
        }
        if(low[now]==dfn[now])
        {
            numdag++;//标记为同一个dag 
            do
            {
                int topp=st[top--];
                dagbh[topp]=numdag;
                   leff[numdag]=min(leff[numdag],canl[topp]);
    //在统计tarjan的同时,把这个强联通分量里面所有的点能到的左右区间范围统计一下 
                rigg[numdag]=max(rigg[numdag],canr[topp]);
                vis[topp]=0;//出栈标记 
            }while(st[top+1]!=now);//dowhile保证能够便历到,因为上面top已经--了所以要判断top+1 
        }
    }

    高清无码完整代码:

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define N 500003
    #define modd 1000000007 
    using namespace std;
    long long read()
    {
        long long ans=0;
        char ch=getchar(),last=' ';
        while(ch<'0'||ch>'9')last=ch,ch=getchar();
        while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
        return last=='-'?-ans:ans;
    }
    int n,id[N],hea[N*4],num,vis[N*4],dagbh[N*4],lef,rig,top,canl[N*4],canr[N*4];
    int st[N*4],dfn[N*4],low[N*4],leff[N*4],rigg[N*4],tarjannum,numdag;
    long long r[N],x[N],ans;
    struct edg{
        int nex,to;
    }edge[N*12];
    inline void add(int from,int to)
    {
        num++;
        edge[num]={hea[from],to};
        hea[from]=num;
    }
    void build(int now,int l,int r)
    {
        canl[now]=l;canr[now]=r;//can表示当前节点能遍历到的左右区间范围 
        if(l==r)//线段树日常操作 
        {
            id[r]=now;
            return;
        }
        int mid=(l+r)>>1;
        build(now<<1,l,mid);build(now<<1|1,mid+1,r);
        add(now,now<<1);add(now,now<<1|1);//不同于线段树的地方,向左右两个节点连边,往下层伸展 
    
    void lb(int mb,int l,int r,int L,int R,int now)
    {
        if(l>=L&&r<=R)
        {
            if(mb==now)//mb表示目标节点,也就是当前初始节点 
                return;
            add(mb,now);//now表示线段树上当前节点 
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)lb(mb,l,mid,L,R,now<<1);//左右区间连边 
        if(R>mid)lb(mb,mid+1,r,L,R,now<<1|1);
    }
    void tarjan(int now)
    {
        dfn[now]=++tarjannum;//dfn都知道是什么吧 
        low[now]=tarjannum;
        vis[now]=1;//设置入栈 
        st[++top]=now;
        for(int i=hea[now];i;i=edge[i].nex)
        {
            int v=edge[i].to;
            if(!dfn[v])
            {
                tarjan(v);low[now]=min(low[now],low[v]);
            }
            else
                if(vis[v]) //在栈中 
                    low[now]=min(low[now],dfn[v]);//以上正常tarjan操作 
        }
        if(low[now]==dfn[now])
        {
            numdag++;//标记为同一个dag 
            do
            {
                int topp=st[top--];
                dagbh[topp]=numdag;
                   leff[numdag]=min(leff[numdag],canl[topp]);
    //在统计tarjan的同时,把这个强联通分量里面所有的点能到的左右区间范围统计一下 
                rigg[numdag]=max(rigg[numdag],canr[topp]);
                vis[topp]=0;//出栈标记 
            }while(st[top+1]!=now);//dowhile保证能够便历到,因为上面top已经--了所以要判断top+1 
        }
    }
    int main()
    {
        memset(leff,0x3f,sizeof(leff));
        n=read();
        for(int i=1;i<=n;i++)
        {
            x[i]=read(),r[i]=read();//x为横轴坐标,r为爆炸半径 
        }
        x[n+1]=0x3f3f3f3f3f3f3f3fll;
        build(1,1,n);
        for(int i=1;i<=n;i++)
        {
            if(!r[i]) continue;
            rig=upper_bound(x+1,x+1+n,x[i]+r[i])-x-1;
            lef=lower_bound(x+1,x+1+n,x[i]-r[i])-x;
            lb(id[i],1,n,lef,rig,1);
        }    
        tarjan(1);//从1号节点往下跑tarjan缩点跑成dag
        for(int i=1;i<=n;i++)
        {
            int fromm=dagbh[id[i]];
            ans=(ans+((long long)i*(rigg[fromm]-leff[fromm]+1))%modd)%modd;//ll
        }
        printf("%lld",ans%modd);
    }

    完结。。

    可能是最水的黑题了其他的都不会做。

     希望讲解过后能对泥萌有所帮助qwq

  • 相关阅读:
    BZOJ1040: [ZJOI2008]骑士
    酱油记:GDKOI2018
    BZOJ1800: [Ahoi2009]fly 飞行棋
    BZOJ3894: 文理分科
    树链剖分
    文件格式问题
    10.23模拟赛
    snakes
    数据结构题目
    jquery&nbsp;easyui&nbsp;datebox&nbsp;的使用&nbsp;.
  • 原文地址:https://www.cnblogs.com/lbssxz/p/11827989.html
Copyright © 2011-2022 走看看