数据结构(储备)

数据结构(树巨结构)

题目1 ShadowIterator与啦啦 操汉子

问题描述

ShadowIterator狂拍啦啦操妹子的照片引起了啦啦操汉子的愤怒,他们吧ShadowIterator引诱到了一个迷宫中准备杀死他!!!
这个迷宫是个有向图,并且时时刻刻都在发生变化。每次变化时,首先是两个节点u,v消失了,并新生成一个节点,新节点的编号是之前出现过的节点数+1;然后原本从u或v出发的边都变成有新节点出发,指向u或v的边都指向新节点。
ShadowIterator很快被这个变化的搞得晕头转向,迷失了方向。他在迷宫里面胡乱的走来走去,因为不知道下一秒迷宫会变成什么样子,所以他完全不知道该向哪个方向走。当他不知道下一步该去哪里的时候他就会向迷宫外的你提问,每次提问是否有从节点u指向v的边,你需要快速回答他的每个问题。
ShadowIterator是死是活就完全靠你了,你作为信息学竞赛的选手,自然能够救出ShadowIterator,不是吗?

输入格式

第一行三个整数N,M,Q,表示这个有向图一开始有N个节点,编号从1到N,有M条有向边,以及Q次操作。
接下来M行,每行两个整数u,v,表示一开始有一条从u指向v的有向边,可能有重边,可能有自环。
接下来Q行每行三个数k,u,v,
·如果k=0表示迷宫发生了变化,u,v节点消失并形成了一个新节点;
·如果k=1表示ShadowIterator向你提问是否有从u指向v的有向边,保证u和v节点都存在。

输出格式

对每次提问输出一行,如果有输出Yes,如果没有输出No。

样例输入

5 7 8
1 2
2 3
3 4
4 5
5 1
1 3
1 4
1 1 4
1 2 5
0 3 5
1 2 6
1 6 4
1 6 2
0 6 2
1 4 7

样例输出

Yes
No
Yes
Yes
No
Yes

提示

样例解释:
一开始迷宫如图1,第三次操作后迷宫如图2,第七次操作后迷宫如图3。(重边只画出了一条)


数据范围:
1<=N<=100000
1<=M<=300000
1<=Q<=200000

题解

 一道好题...可以积累下来这种方法

首先考虑离线做。对于一个新点,是由两个点组合而来的,那么就将这个新点向这两个点连边,这样最后连出来是一个森林,求了dfs序之后发现,组成某个点的某些点在dfs序上的编号一定是连续的,所以可以考虑数据结构,现在要求的是u是否有边连向v也就是组成u的某个点右边连向组成v的点(可能有多个),于是就是两端区间求是否右边,可以将其抽象到坐标轴上,有一个点(x,y)表示在dfs序上的编号为x的点在原图上有向dfs序上编号为y的点连边。这样两段区间就成了一个矩形,求这个矩形(包括边上)是否包含点。然后有很巧妙地用一种方法:求到一个矩形的上边界与下边界,然后将它们与点按纵坐标排序,如果有多个点或边在同一纵坐标上,那么就按照下界最先,端点第二,上边界最后排序。然后一个个遍历,用树状数组维护x轴即可

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 100003;
const int MAXQ = 200003;
struct node{
    int y , k , x[2] , num;
    friend bool operator < ( node a , node b ){
        if( a.y != b.y ) return a.y < b.y;
        return a.k < b.k;
    }
}s[700003];
int op[300003] , u[300003] , v[300003] , dfn[MAXN+MAXQ];
int n ,m ,q , ncnt , L[MAXN+MAXQ] , R[MAXN+MAXQ] , inde , tot;
vector<int>G[MAXN+MAXQ];
bool flag[MAXN+MAXQ];
int ans[MAXQ] , tre[MAXN+MAXQ];
inline void dfs( int x ){
    dfn[x] = ++inde;
    L[x] = inde;
    for( int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        dfs( v );
    }
    R[x] = inde;
}
inline int lowbit( int x ) { return x & -x;}
inline void modify( int x  , int delta){
    for( ; x <= ncnt ; x += lowbit( x ) )
        tre[x] += delta;
}
inline int query( int x , int y ){
    int tot = 0;
    for( ; y ; y -= lowbit( y ) ){
        tot += tre[y];
    }
    for( ; x ; x -= lowbit( x) )
        tot -= tre[x];
    return tot;
}
int main(){
    scanf( "%d%d%d" , &n , &m , &q );
    for( int i = 1 ; i <= m ; i ++ ){
        scanf( "%d%d" , &s[i].x[0] , &s[i].y );
        s[i].k = 1;
    }
    ncnt = n;tot = m;
    for( int i = 1 ; i <= q ; i ++ ){
        scanf( "%d%d%d" , &op[i] , &u[i] , &v[i] );
        if( op[i] == 0 ){
            ncnt ++;
            G[ncnt].push_back( u[i] );
            G[ncnt].push_back( v[i] );
        }
    }
    for( int i = ncnt ; i >= 1 ; i -- ){
        if( !L[i] )
            dfs( i );
    }
    for( int i = 1 ; i <= m ; i ++ ){
        s[i].x[0] = s[i].x[1] = dfn[s[i].x[0]];
        s[i].y = dfn[s[i].y];
    }
    for( int i = 1 ; i <= q ; i ++ ){
        if( op[i] == 1 ){
            s[++tot].x[0] = L[u[i]] , s[tot].x[1] = R[u[i]];
            s[tot].k = 0 , s[tot].y = L[v[i]];
            s[tot].num = i;
            s[++tot].x[0] = L[u[i]] , s[tot].x[1] = R[u[i]];
            s[tot].k = 2 , s[tot].y = R[v[i]];
            s[tot].num = i;
        }
    }
    sort( s + 1 , s + tot + 1 );
    for( int i = 1 ; i <= tot ; i ++ ){
        if( s[i].k == 1 ){
            modify( s[i].x[0] , 1 );
        }
        else if( s[i].k == 0 ){
            ans[s[i].num] = query( s[i].x[0] - 1 , s[i].x[1] );
        }
        else
            ans[s[i].num] = query( s[i].x[0] - 1 , s[i].x[1] ) - ans[s[i].num];
    }
    for( int i = 1 ; i <= q ; i ++ ){
        if( op[i] == 1 ){
            if( ans[i] )
                printf( "Yes\n" );
            else
                printf( "No\n" );
        }
    }
    return 0;
}

 对于大多数人应该可以切掉吧

题目2 部分可持久化动态树

数据范围:
保证文件中出现的所有数字(除n、m外)都为非负整数并且<=10000。

题解

考虑离线,发现直接倍增即可,记录一下每条边的时间戳, 然后判断是否已经大于t即可

那么现在强制在线,怎么办呢?直接选择递归求倍增数组即可

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 100003;
int n , m , b;
int f[MAXN][23] , g[MAXN][23];
bool flag[MAXN][23];
void dfs( int x , int y ){
    if( flag[x][y] ) return ;
    if( !y ) return ;
    dfs( x , y - 1 );
    if( flag[x][y-1] ){
        dfs( f[x][y-1] , y - 1 );
        if( flag[f[x][y-1]][y-1] ){
            f[x][y] = f[f[x][y-1]][y-1];
            g[x][y] = max( g[x][y-1] , g[f[x][y-1]][y-1] );
            flag[x][y] = 1;
        }
    }
}
int main(){
    scanf( "%d%d%d" , &n , &m , &b );
    int lastans = 0;
    for( int i = 1 ; i <= m ; i++ ){
        int op , u ,v , k;
        scanf( "%d" , &op );
        op = ( op + lastans * b % 3) % 3;
        if( !op ){
            scanf( "%d%d" , &u , &v );
            u = ( u + lastans * b % n ) % n + 1;
            v = ( v + lastans * b % n ) %n + 1;
            f[u][0] = v;
            g[u][0] = i;
            flag[u][0] = 1;
        }
        else if( op == 1 ){
            int t;
            scanf( "%d%d%d" , &t , &u , &k );
            t = ( t + lastans * b % m ) % m , u = ( u + lastans * b % n ) % n + 1;
            k = ( k + lastans * b %n ) % n;
            for( int j = 20 ; j >= 0 && u != 0 ; j -- ){
                if( ( 1 << j) <= k ){
                    dfs( u , j );
                    if( f[u][j] > 0 && g[u][j] <= t )
                        u = f[u][j] , k -= ( 1 << j );
                    else
                        u = 0;
                }
            }
            printf( "%d\n" , u );
            lastans = u;
        }
        else{
            int t;
            scanf( "%d%d" , &t , &u );
            t = ( t + lastans * b % m ) % m;
            u = ( u + lastans * b % n ) % n + 1;
            int sum = 0;
            for( int j = 20 ; j >= 0 ; j -- ){
                dfs( u , j );
                if( f[u][j] > 0 && g[u][j] <= t ){
                    u = f[u][j] , sum += ( 1 << j );
                }
            }
            printf( "%d\n" , sum );
            lastans = sum;
        }
    }
    return 0;
}

时间复杂度O(Nln^{2}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值