• 2019牛客暑期多校训练营(第八场) E I


    E Explorer

    题意:给出一个无向图,每条边有一个通过人数的上限和下限,一群人要一起从1号点走到n号点,这一群人一起走不能分开,问这群人的人数有多少种可以满足条件。

    解法:不会做题解参考https://blog.csdn.net/qq_41955236/article/details/99229810这位大佬的,讲得巨好。简单来说就是把原区间离散化(注意这里要有特殊的离散化技巧,①把原区间右端点+1②原区间就代表离散化后的[l,r-1],③此时离散化后的每个点代表的是该点到该点右边一个点的区间,即该点的贡献是r[i+1]-l[i])。)把离散化后的下标建一棵线段树,然后把原图上的边插入到相应的区间结点上(如[1,3]这条边就插入到[1,2][3,4]这两个结点)。然后就在线段树上分治寻找满足要求的答案区间,这一步具体来说就是从跟开始不断往下分治,把当前点存下的边加入到图中,如果此时的图1到n连通统计答案分治停止,否则继续分治直至连通或到叶子结点。

    怎么一边分治一边判断连通呢?这里用到并查集判断连通的技巧,从上往下加边就在并查集把边的两个端点集合合并,然后如果1和n在同个集合就是连通。然后注意回溯的时候还原并查集,同时因为有回溯操作,并查集不能路径压缩而使用启发式合并。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    typedef long long LL;
    int n,m,fa[N],sz[N];
    LL ans;
    struct edge{
        int u,v,l,r;
    }e[N];
    
    int getfa(int x) { return x==fa[x] ? x : getfa(fa[x]); }
    
    vector<int> tree[N<<2],b;
    void update(int rt,int l,int r,int ql,int qr,int id) {
        if (ql<=l && r<=qr) {
            tree[rt].push_back(id);
            return;
        }
        int mid=(l+r)>>1;
        if (ql<=mid) update(rt<<1,l,mid,ql,qr,id);
        if (qr>mid) update(rt<<1|1,mid+1,r,ql,qr,id);
    } 
    
    void dfs(int rt,int l,int r) {
        vector<int> rec; rec.clear();
        int mid=(l+r)>>1;
        for (int i=0;i<tree[rt].size();i++) {
            int x=e[tree[rt][i]].u,y=e[tree[rt][i]].v;
            int fx=getfa(x),fy=getfa(y);
            if (sz[fx]>sz[fy]) swap(fx,fy);
            rec.push_back(fx);
            fa[fx]=fa[fy]; sz[fy]+=sz[fx];
        }
        if (getfa(1)==getfa(n)) ans+=b[r]-b[l-1];  //每个点代表的是该点到右边一个点的区间值 
        else if (l<r) dfs(rt<<1,l,mid),dfs(rt<<1|1,mid+1,r);
        for (int i=0;i<rec.size();i++) fa[rec[i]]=rec[i];
    }
    
    int main()
    {
        cin>>n>>m;
        for (int i=1;i<=m;i++) {
            scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].r);
            b.push_back(e[i].l); b.push_back(e[i].r+1);  //右端点+1 
        }    
        sort(b.begin(),b.end());
        b.erase(unique(b.begin(),b.end()),b.end());
        for (int i=1;i<=m;i++) {
            int l=lower_bound(b.begin(),b.end(),e[i].l)-b.begin()+1;
            int r=lower_bound(b.begin(),b.end(),e[i].r+1)-b.begin()+1;
            update(1,1,b.size(),l,r-1,i);  //原区间离散化后是[l,r-1] 
        }
        
        for (int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
        dfs(1,1,b.size());
        cout<<ans<<endl; 
        return 0;
    } 
    View Code

    D Distance

    题意:在一个n*m*h (n*m*h<=1e5) 的三维平面上,有两种操作,操作一是在某个点打个标记,操作二是询问距离某个点最近的标记输出距离。

    解法:比赛的时候写了过KD-tree果断TLE了。赛后才知道正解是定期重构。

    具体来说:先是我们定义一个dis数组就是所有操作点作为起点在图上跑bfs得到的最小距离。那么我们先用一个队列v把打标记操作囤起来,当这个队列的标记操作达到sqrt(n*m*h)的时候,把队列里的所有元素(有sqrt(n*m*h)个)作为起点跑一次bfs去更新dis数组,没有达到sqrt(nmh)个的时候就先不管先囤着。那么怎么处理询问呢?对于一个询问它的答案ans首先等于dis[x][y][z],这是代表已经在图上跑过bfs的点(已经屯好的点)的答案,那么还有还在囤在队列v的点就直接暴力更新,因为此时队列v的点不会超过sqrt(nmh)个也不会太慢。

    定期重构思想有点像分块,只不过是把操作分块了而不是像分块那样把数据分块。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    const int dx[]={-1,1,0,0,0,0};
    const int dy[]={0,0,-1,1,0,0};
    const int dz[]={0,0,0,0,-1,1};
    struct dat{ int x,y,z; };
    int n,m,h,T,dis[N];
    vector<dat> v;
    
    int id(int x,int y,int z) { return (x-1)*m*h+(y-1)*h+z; }
    
    queue<dat> q;
    void bfs() {
        while (!q.empty()) q.pop();
        for (int i=0;i<v.size();i++) {
            q.push((dat){v[i].x,v[i].y,v[i].z});
            dis[id(v[i].x,v[i].y,v[i].z)]=0;
        }
        while (!q.empty()) {
            dat u=q.front(); q.pop();
            for (int i=0;i<6;i++) {
                int nx=u.x+dx[i],ny=u.y+dy[i],nz=u.z+dz[i];
                if (nx<1 || nx>n || ny<1 || ny>m || nz<1 || nz>h) continue;
                if (dis[id(nx,ny,nz)]>dis[id(u.x,u.y,u.z)]+1) {
                    dis[id(nx,ny,nz)]=dis[id(u.x,u.y,u.z)]+1;
                    q.push((dat){nx,ny,nz});
                }
            }
        }
    }
    
    int main()
    {
        cin>>n>>m>>h>>T;
        int k=(int)sqrt(n*m*h);
        memset(dis,0x3f,sizeof(dis));
        while (T--) {
            int opt,x,y,z; scanf("%d%d%d%d",&opt,&x,&y,&z);
            if (opt==1) {
                v.push_back((dat){x,y,z});
                if (v.size()>=k) {  //定期重构 
                    bfs();
                    v.clear();
                }
            } else {
                int ans=dis[id(x,y,z)];
                for (int i=0;i<v.size();i++)
                    ans=min(ans,abs(x-v[i].x)+abs(y-v[i].y)+abs(z-v[i].z));
                printf("%d
    ",ans);    
            }
        }
        return 0;
    }
    View Code

    另外还有一种思维上比较”暴力“的解法:分八种情况建立三维BIT查询最近点对。不难理解这种办法是对的,其实就是分八字情况把绝对值符号去掉了。

    但是这种办法实现起来可能会有比较多的技巧,首先就是用怎样的数据结构来存储n*m*h<=1e5的三维BIT?这里用两种办法:第一是常见的通过映射把三维拍成一维数组方式,第二是用三重vector来实现不定长三维数组。然后下一个技巧就是怎么快速讨论八种情况,这里一个比较巧妙地办法是通过0-7地二进制位来决定x/y/z的正负,同时也是用0-7的二进制决定查询x/yz的前半部分还是后半部分。

    代价几乎就是抄袭(读书人的事。咳咳)https://www.cnblogs.com/Cwolf9/p/11333344.html这位大佬的。

    #include<bits/stdc++.h>
    using namespace std;
    typedef vector<int> VI;
    typedef vector<VI> VVI;
    typedef vector<VVI> VVVI;
    const int INF=0x3f3f3f3f;
    int n,m,h,T;
    
    struct BIT{
        int n,m,h;
        VVVI bit;
        void init(int _n,int _m,int _h) {  //初始化好大小就可以直接当作三维数组使用 
            n=_n; m=_m; h=_h;
            bit=VVVI(n+1,VVI(m+1,VI(h+1,INF)));
        }
        void update(int x,int y,int z,int v) {
            for (int i=x;i<=n;i+=i&-i) 
                for (int j=y;j<=m;j+=j&-j)
                    for (int k=z;k<=h;k+=k&-k)
                        bit[i][j][k]=min(bit[i][j][k],v);
        }
        int query(int x,int y,int z) {
            int ret=INF;
            for (int i=x;i;i-=i&-i)
                for (int j=y;j;j-=j&-j)
                    for (int k=z;k;k-=k&-k)
                        ret=min(ret,bit[i][j][k]);
            return ret;            
        }
    }bit[8];
    
    int main()
    {
        cin>>n>>m>>h>>T;
        for (int i=0;i<8;i++) bit[i].init(n,m,h);  //这里很重要 
        while (T--) {
            int opt,x,y,z; scanf("%d%d%d%d",&opt,&x,&y,&z);
            if (opt==1) {
                for (int i=0;i<8;i++) {
                    int v=((i&1)?x:-x)+(((i>>1)&1)?y:-y)+(((i>>2)&1)?z:-z);
                    bit[i].update((i&1)?n+1-x:x,((i>>1)&1)?m+1-y:y,((i>>2)&1)?h+1-z:z,v);            
                }
            } else {
                int ans=INF; 
                for (int i=0;i<8;i++) {
                    int v=((i&1)?x:-x)+(((i>>1)&1)?y:-y)+(((i>>2)&1)?z:-z);
                    int tmp=bit[i].query((i&1)?n+1-x:x,((i>>1)&1)?m+1-y:y,((i>>2)&1)?h+1-z:z);
                    ans=min(ans,-v+tmp);
                }
                printf("%d
    ",ans);
            }
        }
        return 0;
    }
    View Code
  • 相关阅读:
    laravel数据库配置
    mysql中utf8和utf8mb4区别
    laravel中artisan的用法
    laravel项目composer安装
    Linux 文件描述符
    Centos 7/8 SELinux
    Centos 7/8 高级磁盘管理技术
    Centos 7/8 搭建NFS Server
    Centos 7/8 日志管理
    Centos 7/8 Chronyd时间同步
  • 原文地址:https://www.cnblogs.com/clno1/p/11469830.html
Copyright © 2020-2023  润新知