首页 > 其他分享 >CF1401E Divide Square 题解

CF1401E Divide Square 题解

时间:2024-02-07 10:15:32浏览次数:25  
标签:CF1401E Square 边上 题解 线段 la 横坐标 横线 端点

解题思路

其实多看几组也能发现块数等于交点的数量加上两个端点都在边上的线段数量再加一。

证明如下(图见样例):

  • 对于两条只有一个端点位于边上的线段,因为保证有一个端点位于边上,那么这两条线段的交点一定会和已存在的点、边构成一个新的矩形;
  • 对于其中有一条为两个端点均位于边上的两条线段,两个端点均位于边上的线段会与已存在的两边构成两个矩形;
  • 剩下的不规则部分产生 \(1\) 的贡献。

此时问题转化为如何计算出交点数量。

考虑枚举横坐标,然后统计出此时这个坐标上有多少条横向线段,然后判断是否有一条竖线的横坐标为当前横坐标,如果有,则分以下两种情况计算:

  1. 存在一个端点位于 x 轴上,那么直接将答案加上纵坐标小于等于 \(r_i\) 的横线数量即可;
  2. 其他情况,不妨设当前横坐标上有 \(sum\) 条横向线段,那么答案应加上 \(sum\) 减去纵坐标小于 \(l_i\) 的横线数量。

那么,我们需要做到快速维护横向线段。考虑使用树状数组。设当前横坐标为 \(x\),设横线段左端点横坐标为 \(la_i\),右端点横坐标为 \(ra_i\),纵坐标为 \(y_i\)。那么我们分别对于横线按照 \(la_i\) 从小到大和 \(ra_i\) 从大到小排序,然后判断若 \(la_i=x\),那么在 \(y_i\) 位置加上 \(1\);若 \(ra_1=x\),那么在 \(y_i\) 位置减去 \(1\)。然后判断若 \(x\) 处有纵线段,那么直接查找即可。

本题步骤如下:

  • 分别存储横、纵线段,横线段存两份;
  • 将纵线段按 \(x\) 排序,两份横线段分别按 \(la_i\) 从小到大和 \(ra_i\) 从大到小排序;
  • 枚举 \(x\),更新当前位置上的横线段数量;
  • 判断是否存在一条竖线段,若有则按上文所讲方法更新 \(ans\) 即可;
  • 枚举每条线段,若该线段两个端点都在正方形边上,那么 \(ans\gets ans+1\);
  • 输出 \(ans+1\);

注意事项

  • 不开 long long 见祖宗;
  • 不需要考虑树状数组越界问题;
  • 先添加线段,然后计算,最后再删;
  • 在计算 \(r\) 位于边上的线段时,一定要查询小于等于 \(l-1\) 的横线数量,而不是 \(l\)。

AC 代码

#include<set>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define int long long
#define N 100005
#define mint 0
#define maxt 1000000
int n,m;
struct Line{
    int y,l,r;
}a[N],d[N];
struct lIne{
    int x,l,r;
}b[N];
inline bool cmp(Line A,Line B){
    return A.l<B.l;
}
inline bool Cmp(lIne A,lIne B){
    return A.x<B.x;
}
inline bool CMP(Line A,Line B){
    return A.r<B.r;
}
struct BitTree{
    int val[(maxt<<2)+5];
    #define lowbit(x) (x&(-x))
    inline void Add(int x,int v){
        while(x<=maxt){
            val[x]+=v;
            x+=lowbit(x);
        }
    }
    inline int Query(int x){
        int res=0;
        while(x){
            res+=val[x];
            x-=lowbit(x);
        }return res;
    }
}tree;
signed main(){
    scanf("%lld%lld",&n,&m);
    for(register int i=1;i<=n;++i){
        scanf("%lld",&a[i].y);
        scanf("%lld",&a[i].l);
        scanf("%lld",&a[i].r);
    }
    for(register int i=1;i<=m;++i){
        scanf("%lld",&b[i].x);
        scanf("%lld",&b[i].l);
        scanf("%lld",&b[i].r);
    }
    for(register int i=1;i<=n;++i)
        d[i]=a[i];
    std::sort(a+1,a+n+1,cmp);
    std::sort(d+1,d+n+1,CMP);
    std::sort(b+1,b+m+1,Cmp);
    int la=1,ra=1,lb=1,ans=0,cnt=0;
    for(register int x=mint;x<=maxt;++x){
        while(a[la].l==x&&la<=n){
            tree.Add(a[la].y,1);
            ++la;++cnt;
        }
        if(x==b[lb].x){
            if(b[lb].l==mint&&b[lb].r==maxt)
                ++ans;
            if(b[lb].l==mint)
                ans+=tree.Query(b[lb].r);
            else
                ans+=cnt-tree.Query(b[lb].l-1);
            ++lb;
        }while(d[ra].r==x&&ra<=n){
            tree.Add(d[ra].y,-1);
            ++ra;--cnt;
        }
    }for(register int i=1;i<=n;++i)
        if(a[i].l==mint&&a[i].r==maxt)
            ++ans;
    printf("%lld",ans+1ll);
}

标签:CF1401E,Square,边上,题解,线段,la,横坐标,横线,端点
From: https://www.cnblogs.com/UncleSamDied6/p/18010661

相关文章

  • CF1401F Reverse and Swap 题解
    解题思路巧妙的数据结构题,非常巧妙的利用的线段树的奇妙性质。操作逐条维护:Replace:直接线段树上单点修改;Sum:线段树查询区间和;Reverse:考虑线段树的形态。线段树第\(i\)层(除最后一层)有\(2^{i-1}\)个节点,那么将所有\(i\ge1\)的区间\([(i-1)\times2^k,i\times2^k]\)......
  • CF1408E Avoid Rainbow Cycles 题解
    解题思路第一眼看过去感觉不是很可做……但是我们可以发现,如果有两个点在不同的集合中出现过,那么一定会存在彩虹环,那么两个点最多出现一次。同时我们考虑将题意转化一下,变成求最大能选取的点,使得不出现彩虹环。根据刚刚的性质,我们可以考虑每个点向它所在的集合连一条边权为\(a_......
  • CF1338C Perfect Triples 题解
    解题思路没什么好说的,就是打表找规律……表在这里不难发现,三元组中第一个数的最后两位按照\(00\to01\to10\to11\)的顺序变化,其他位也一样,同样,第二个数和第三个数中每两位分别按照\(00\to10\to11\to01\)和\(00\to11\to01\to10\)的顺序变化,且与第一个数对应变化......
  • CF1379C Choosing flowers 题解
    解题思路不是那么显然的,当只选一种\(b_i\)或全选\(a_i\)时最优。那么我们可以先对\(a_i\)从大到小排序,枚举每一个\(b_i\),然后二分找到第一个大于等于\(b_i\)的\(a_j\),判断\(a_1\sima_j\)中是否包含\(a_i\),如果包含,当前的答案为\(\displaystyle\left(\sum_{k=1}^{......
  • CF1385F Removing Leaves 题解
    解题思路简单贪心,优先选择叶子节点最多的,这样能够保证一定能把所有能删的都删了。因为要建一个可删除的图,所以我们可以使用set来存边,不然就需要维护一堆东西……那么我们肯定是从有叶子节点的点向父亲更新的,那么我们每次选择叶子节点最多的点,然后删除\(k\)个叶子,判断一下删......
  • CF1415E New Game Plus! 题解
    解题思路简单贪心题,我们可以把整个序列看作\(k+1\)个可重集,首先可以得到一个显然的结论:较大的数一定比较小的数先放入一个集合中。同样,由于每一轮\(ans\getsans+\maxsum_i\),其中\(sum_i\)表示第\(i\)个集合的元素和,那么,我们一定会将当前的元素放入当前和最大的哪个集合......
  • CF1416B Make Them Equal 题解
    解题思路观察可以发现,每次操作后序列元素之和不变,那么我们可以将每一次操作看作是\(a_i\)向\(a_j\)移动了\(i\timesx\)。由此可得,若序列和\(sum\bmodn\not=0\),那么一定无解,否则一定存在一个合法的操作方案。因为每次移动时移动的数应为\(i\)的倍数,所以\(a_1\)可以向......
  • CF1446C Xor Tree 题解
    解题思路与其考虑删除哪些点,不如考虑保留哪些点。考虑到和异或有关,那么我们可以把这些数倒序插入trie树中,然后我们就可以在trie树上跑一个简单的dp:若当前节点为叶子节点,那么保留,返回\(1\);若当前节点在链上,那么直接继承儿子节点;若当前节点有两个儿子,那么更新为较大儿子......
  • CF1922E Increasing Subsequences 题解
    解题思路因为可以有空集,那么我们首先构造第一段最长的连续上升的序列,那么这段序列中共有\(2^{\mids\mid}\)个上升子序列。接下来我们考虑补全剩余的,我们不妨将剩余的部分全部设为连续不增序列,那么设当前位置在第一段中有\(k\)个小于它的,那么添加这个数后可以增加\(2^{k-1}......
  • CF1413C Perform Easily 题解
    解题思路其实是很简单的一道题,考虑计算出所有\(b_i\)在减去每一个\(a_j\)后所有可能的值,将这个值按照从小到大的顺序排序,那么我们可以考虑固定最小值,查找最大值,这个时候从最小值到最大值的区间内应该每一种\(b_i\)都出现了一次。那么,我们可以使用一个桶或者map搭配双指针......