【UVA1455】Kingdom

题意

  有 n 个点和m个操作,分为两种:
  ①. road u v表示连接 u 号点和v号点
  ②. line C C为小数,并且小数部分一定为0.5)表示询问 y=C 这条直线穿越了几个联通块,这些联通块的点数之和是多少
   n105m2105

解法

并查集+线段树:
  一开始错的很惨啊……不过最后还是自己改出来了……
  
  运行速度还是挺快的,在这里排 Rank3
  对于连边操作,应该想到并查集,同时维护每一个并查集的 siz ,这个很容易实现,难点是②操作:
  因为询问比较多,所以单次询问的复杂度应该在 logn 级别,大家应该都会想到线段树
  对于一个联通块 S ,考虑再维护两个信息:下边界和上边界(y轴方向上),在合并两个并查集的时候,同时更新这两个信息:

  然后我们就可以将每一个联通块对应在线段树上的一个区间,这样我们就可以利用线段树做到 log 的查询,下面考虑怎么维护区间信息:
  假设目前要合并两个联通块 S U,那么应该首先去除掉这两个联通块原本的贡献,然后合并之后加上新联通块的贡献,这样就不会有重复
  所以我们在线段树中记录区间左右端点,完全覆盖了这一个区间的联通块个数以及这些联通块中的点数之和(完全覆盖指这一个区间是某个联通块的覆盖范围的子集)
  在进行区间操作时,因为覆盖的区间长度可能比较大,所以肯定不能够暴力覆盖所有的单点,而是要打 lazy 标记。不过本题的区间维护很特殊,并不能够通过子节点来更新父节点,而只能够通过父节点更新子节点:因为如果某个联通块完全覆盖了某个区间 u ,但是并不一定完全覆盖了u的父节点,相反, u 的子节点一定会被完全覆盖
  然后还有一个问题就是C是一个小数,这让我们很恼火。不过可以将每一个点的纵坐标都乘以2,然后 C 也乘以2,这样的话就可以把所有的点变为整数,不过相应的,空间也要开两倍大(我就是这里被坑了好久……数组开小了可能是RE,可能会 WA ,也有可能导致 TLE

复杂度

O( Tmlogn

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#define ls 2*k
#define rs 2*k+1
#define Rint register int
#define Lint long long int
using namespace std;
const int N=2000100;
int up[N],down[N];
int f[N],siz[N];
int T,n,m;
struct Segment_tree
{
    struct node
    {
        int l,r;
        int cnt,siz;
    }t[N*4];
    struct task
    {
        int cnt,siz;
    }A;
    void build(Rint k,Rint l,Rint r)
    {
        t[k].l=l,t[k].r=r;
        if( l==r )   return ;
        int mid=(l+r)/2;
        build( ls,l,mid ),build( rs,mid+1,r );
    }
    void pushdown(Rint k)
    {
        if( ls>N*4 )   return ;
        int &x=t[k].cnt,&y=t[k].siz;
        t[ls].cnt+=x,t[rs].cnt+=x;
        t[ls].siz+=y,t[rs].siz+=y;
        x=y=0;
    }
    void insert(Rint k,Rint l,Rint r,Rint x,Rint w)
    {
        if( ( k==1 && l==r ) || l>r )   return ;
        if( t[k].l==l && t[k].r==r )
        {
            t[k].cnt+=w,t[k].siz+=w*siz[x];
            return ;
        }
        pushdown( k );
        if( r<=t[ls].r )   insert( ls,l,r,x,w );
        else
            if( l>=t[rs].l )   insert( rs,l,r,x,w );
            else   insert( ls,l,t[ls].r,x,w ),insert( rs,t[rs].l,r,x,w );
    }
    task query(Rint k,Rint x)
    {
        if( t[k].l==t[k].r )   return (task){ t[k].cnt,t[k].siz };
        pushdown( k );
        if( x<=t[ls].r )   return query( ls,x );
        else   return query( rs,x );
    }
    void clear(Rint k,Rint l,Rint r)
    {
        t[k].l=0,t[k].r=0;
        t[k].cnt=t[k].siz=0;
        if( l==r )   return ;
        int mid=(l+r)/2;
        clear( ls,l,mid ),clear( rs,mid+1,r );
    }
}Tr;
int find(int x)
{
    if( x!=f[x] )   f[x]=find( f[x] );
    return f[x];
}
void Union(int u,int v)
{
    f[u]=v;
    siz[v]+=siz[u];
    up[v]=max( up[v],up[u] );
    down[v]=min( down[v],down[u] );
}
int main()
{
    char s[10];
    double x;
    int u,v,mx;
    scanf("%d",&T);
    while( T-- )
    {
        scanf("%d",&n);
        mx=0;
        for(int i=1;i<=n;i++)   f[i]=i,siz[i]=1;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&u,&v);
            up[i]=down[i]=v*2;
            mx=max( mx,v );
        }
        mx=mx*2;
        Tr.build( 1,1,mx );
        scanf("%d",&m);
        while( m-- )
        {
            scanf("%s",s);
            if( s[0]=='r' )
            {
                scanf("%d%d",&u,&v);
                u++,v++;
                u=find( u ),v=find( v );
                if( u!=v )
                {
                    Tr.insert( 1,down[u]+1,up[u],u,-1 );
                    Tr.insert( 1,down[v]+1,up[v],v,-1 );
                    Union( u,v );
                    Tr.insert( 1,down[v]+1,up[v],v,1 );
                }
            }
            else
            {
                scanf("%lf",&x);
                v=x*2;
                if( v>mx )   { printf("0 0\n");continue ; }
                Tr.A=Tr.query( 1,v );
                printf("%d %d\n",Tr.A.cnt,Tr.A.siz);
            }
        }
        Tr.clear( 1,1,mx );
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值