[YZOJ]P3290-暴力-性质题

链接


题意描述
给定一个 h×w 的网格图,m个互不相交(定义相交为: AReca,ARecb AReca,ARecb )的矩形,n个点对。
当两点可以不经过矩形相互到达时,称这个点对合法。
求合法的点对数。
这里写图片描述


题解
最初的想法就是连接合法边,对于一个点dfs一遍,对可以到达的点用并查集维护为一个集合,这个做法复杂度大概O(hw),姿势正确的话有60分。

我们可以发现一个性质。由于矩形互不相交,则:
如果两个点属于的最小的矩形相同,则两个点一定可以连通。
且易证其为充要条件。

于是我们就可以算出两个点的最小包含矩形,并判断它们是否相等即可。
这个做法时间复杂度为O(nm)。虽然常数优化了不少,代码难度也瞬间下降为普及组第一题,但依然只有60分。

在真正的赛场上,除了想到正解,想到一中易于实现的做法也是一个成功。采用相对容易的算法,时间还是风险都有所下降。

这个算法的瓶颈在计算最小矩形的时间,每做一次都要花费O(m)的时间,一看就很有优化的空间!我们如何用更优秀的时间算出每个点所属的矩形呢?

最小矩形作为一个集合元素,不易于代数的描述和记录。我们尝试改变表示方法,这就要求我们找出属于同一个最小矩形的点的通性,即一个条件p,作为两个点属于同一个最小矩形的充要条件。

如果给每一个矩形随机一个编号,对包含其的所有矩形的编号以某种方法求一个哈希值,不就可以唯一的表示这些点了吗?
想法很好!接着我们就可以开始优化了。

优化O(m)时间的很常用的做法就是记前缀和。然而,一维的前缀和是对于一一维的线而言的。对于二维的平面,我们需要对其分治,这也决定了它无法达到一维O(1)效率的转移,而需要O(log_n)。

这个时候我们遇到了难以逾越的障碍,因为我们很难确定一个排序的方法,自然也就无法分治秋前缀和。

我们发现,“包含”是个不易表示的概念。有没有办法用其他方式表示这个概念呢?

这里写图片描述

对于绿点,属于矩形P1,在其左上角所包含的区域里面,有P1的点奇数个,P2、P3的点偶数个。
对于粉点,属于矩形P1、P2,在其左上角包含P1、P2的点奇数个,P3的点偶数个。
规律很明显,也易证明。

看到奇偶性我们很容易想到异或操作的性质。我们对于每一个矩形的四个点随机一个哈希值,就可以表示每一个,对一个要求的点的左上方位置的所有端点取一个异或和作为哈希值,就可以很大概率唯一的表示此点和此点所属点集的位置关系了。
我们就是希望将一个个复杂的矩形转化一个个简单的点,化整为零,使其易于操作,因为我们要一个排序的方法。而我们知道,对于点的排序是很容易的。

具体的分治方法是先对其x坐标进行排序,再对其进行分治。考虑分治[l,r]为[l,mid]和[mid+1,r],已经分治完了左边,对于属于右边的每一个要求的点,要把左边y小于等于它的点全部取计入它的哈希值(此时左边经过原来对x的排序已经可以保证x都小于要求的这个点的x了)。
可以证明,迭代log_n次后,所有范围内的点都可以被计入它的哈希值(想想为什么)。

最后比较一下这个点对的哈希值。
时间复杂度分析
最初的对x排序由于x不超过 107 ,桶排序可以O(h)过。分治迭代llog_n层,每层询问要对n个点贡献哈希值,每次贡献需要求y恰好小于等于这个点y值的点,可以用一个log_n二分找出。
总时间复杂度 O(h+nlog2n)
问题就完美的解决了。

(最后一个点奇怪WA掉的代码)

#include<cstdio>
#include<algorithm>

#define R register
#define max_rand 1000010
using namespace std;
struct ND{int x,y,hash,s,num;}a[3000010],c[3000010];
struct ANS{int p1,p2;}b[500010];
struct ORD{int nex,key;}f[3000010];
int n,m,h,w;
int at=0,ft=0;
int st[10000010];

int read()
{
    R int xx;R char ch;
    while(ch=getchar(),ch<'0'||ch>'9');xx=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')xx=xx*10+ch-'0';
    return xx;
}

bool cmp(const ND &a1,const ND &a2){return a1.num<a2.num;}

int higher_bound(R int l,R int r,R int lim)
{
    R int m;
    while(r-l>1)
    {
        m=(l+r)>>1;
        if(a[m].y<=lim)l=m;
        else r=m-1;
    }
    if(a[l].y>lim)return 0;
    return a[r].y<=lim?r:l;
}

void merge_sort(R int l,R int r)
{
    if(l==r)
    {
        if(a[l].num==0)a[l].s=a[l].hash;
        return;
    }
    R int i,j,k,mid=(l+r)>>1;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    for(i=mid+1;i<=r;++i)if(a[i].num!=0)
    {
        k=higher_bound(l,mid,a[i].y);
        a[i].hash^=a[k].s;
    }
    i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
    {
        if(a[i].y<=a[j].y)c[k++]=a[i++];
        else c[k++]=a[j++];
    }
    while(j<=r)c[k++]=a[j++];
    while(i<=mid)c[k++]=a[i++];
    a[l]=c[l],a[l].s=(a[l].num==0?a[l].hash:0);
    for(i=l+1;i<=r;++i)
        a[i]=c[i],a[i].s=a[i-1].s^((a[i].num==0)?a[i].hash:0);
}

int main()
{
    n=read(),m=read(),h=read(),w=read();
    R int i,j,k,e;
    R int x1,y1,x2,y2;
    for(i=1;i<=n;++i)
    {
        x1=read(),y1=read(),x2=read(),y2=read();
        c[++at]=(ND){x1,y1,0,0,i};
        c[++at]=(ND){x2,y2,0,0,i};
        b[i].p1=b[i].p2=-1;
    }
    for(i=1;i<=m;++i)
    {
        x1=read(),y1=read(),x2=read(),y2=read();
        k=rand()%max_rand;
        c[++at]=(ND){x1,y1,k,0,0};
        c[++at]=(ND){x1,y2+1,k,0,0};
        c[++at]=(ND){x2+1,y1,k,0,0};
        c[++at]=(ND){x2+1,y2+1,k,0,0};
    }
    for(i=1;i<=at;++i)
        f[++ft]=(ORD){st[c[i].x],i},st[c[i].x]=ft;
    for(at=0,i=0;i<=10000002;++i)
        for(e=st[i];e!=0;e=f[e].nex)
            a[++at]=c[f[e].key];
    merge_sort(1,at);
    for(i=1;i<=at;++i)
        if(a[i].num!=0)
            (b[a[i].num].p1==-1)?(b[a[i].num].p1=a[i].hash):(b[a[i].num].p2=a[i].hash);
    for(i=1;i<=n;++i)
    {
        if(b[i].p1==b[i].p2)puts("0");
        else puts("1");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值