2021牛客暑期多校训练营6 H.Hopping Rabbit(扫描线)

题目链接:https://ac.nowcoder.com/acm/contest/11257/H

分析

每一行的全部扫描线都加入后,判断线段树总点的sum是否是小于d的,如果是则答案就在这一行中,递归去找答案即可。

代码

#include<bits/stdc++.h>
using namespace std;
//#define debug 1

const int N=1e6+10;
typedef long long LL;
int lazy[N];
struct node1
{
    int l,r,sum;
}tr[N<<2];
struct node2
{
    int x1,x2,y;
    int flag;
}p[N<<3];
bool cmp(node2 a,node2 b)
{
   if(a.y!=b.y) return a.y<b.y;
   return a.flag>b.flag;
}
int tot=0;
void pushup(int x)
{
   if(lazy[x]) tr[x].sum=tr[x].r-tr[x].l+1;
   else tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum;
   return ;
}
void add(int x1,int y1,int x2,int y2)
{
    p[++tot].x1=x1,p[tot].x2=x2,p[tot].y=y1,p[tot].flag=1;
    p[++tot].x1=x1,p[tot].x2=x2,p[tot].y=y2+1,p[tot].flag=-1;
}
void build(int x,int l,int r)   //建树
{
    tr[x].l=l,tr[x].r=r;
    if(l==r)
    {
        tr[x].sum=0;
        return ;
    }
    int mid=(l+r)/2;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
}
void update(int x,int st,int ed,int flag)
{
    if(tr[x].l>=st&&tr[x].r<=ed)
    {
        lazy[x]+=flag;
        pushup(x);
        return ;
    }
    int mid=(tr[x].l+tr[x].r)>>1;
    if(st<=mid) update(x<<1,st,ed,flag);
    if(ed>mid) update(x<<1|1,st,ed,flag);
    pushup(x);
}
int query(int x)
{
    if(tr[x].l==tr[x].r) return tr[x].l;
    if(tr[x<<1].sum<tr[x<<1].r-tr[x<<1].l+1) return query(x<<1);
    else return query(x<<1|1);
}
int main()
{
	#ifdef debug
	freopen("15.in", "r", stdin);
	freopen("out.txt", "w", stdout);
	#endif
    int n,d;
    scanf("%d%d",&n,&d);
    for(int i=1;i<=n;i++)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        int d1 = x2 - x1;
        int d2 = y2 - y1;
        x1 = (x1 % d + d) % d;
        y1 = (y1 % d + d) % d;
        x2 = x1 + d1 - 1;
        y2 = y1 + d2 - 1;
        if(x1 < d && x2 < d && y1 < d && y2 < d)
        {
        	add(x1, y1, x2, y2);
		}
		else if(x1 < d && x2 >= d && y1 < d && y2 < d)
		{
			if(d1 >= d) add(0, y1, d-1, y2);
			else add(0, y1, x2 - d, y2), add(x1, y1, d - 1, y2);
		}
		else if(x1 < d && x2 < d && y1 < d && y2 >= d)
		{
			if(d2 >= d) add(x1, 0, x2, d - 1);
			else add(x1, 0, x2, y2 - d), add(x1, y1, x2, d - 1);
		}
		else if(x1 < d && x2 >= d && y1 < d && y2 >= d)
		{
			if(d1 < d && d2 < d)
			{
				add(x1, y1, d - 1, d - 1);
				add(x1, 0, d - 1, y2 - d);
				add(0, 0, x2 - d, y2 - d);
				add(0, y1, x2 - d, d - 1);
			}
			else if(d1 >= d && d2 < d)
			{
				add(0, 0, d - 1, y2 - d);
				add(0, y1, d - 1, d - 1);
			}
			else if(d1 < d && d2 >= d)
			{
				add(0, 0, x2 - d, d - 1);
				add(x1, 0, d - 1, d - 1);
			}
			else add(0, 0, d - 1, d - 1);
		}
    }
    sort(p+1,p+1+tot,cmp);
    if(p[1].y!=0)
    {
        printf("YES\n");
        printf("0 0\n");
    }
    else if(p[tot].y!=d)
    {
        printf("YES\n");
        printf("%d %d\n",d-1,d-1);
    }
    else
    {
        build(1,0,d-1);
        int idx=1;
        update(1,p[1].x1,p[1].x2,p[1].flag);
        int flag=0;
        while(idx<=tot)
        {
            while(p[idx].y==p[idx+1].y&&idx+1<=tot) 
            {    
        		idx++;
                update(1,p[idx].x1,p[idx].x2,p[idx].flag);
            }
            if(tr[1].sum<d)
            {
                printf("YES\n");
                printf("%d %d\n",query(1),p[idx].y);
                flag=1;
                break;
            }
            idx++;
            if(idx>tot) break;
            if(p[idx].y>=d) break;
            update(1,p[idx].x1,p[idx].x2,p[idx].flag);
        }
        if(flag==0) printf("NO\n");
    }
    //system("pause");
    return 0;
}

扫描线模板

模板代码摘自:https://blog.csdn.net/dajiangyou123456/article/details/104511472

求面积
//  code for P5490
//  代码没有挖坑
#include <stdio.h>
#include <iostream>
#include <algorithm>
#define lson (x << 1)
#define rson (x << 1 | 1)
using namespace std;
const int MAXN = 1e6 + 10;
typedef long long ll;

int n, cnt = 0;
ll x1, y1, x2, y2, X[MAXN << 1];

struct ScanLine {
    ll l, r, h;
    int mark;
//  mark用于保存权值 (1 / -1)
    bool operator < (const ScanLine &rhs) const {
        return h < rhs.h;
    }
} line[MAXN << 1];

struct SegTree {
    int l, r, sum;
    ll len;
//  sum: 被完全覆盖的次数;
//  len: 区间内被截的长度。
} tree[MAXN << 2];

void build_tree(int x, int l, int r) {
//  我觉得最不容易写错的一种建树方法
    tree[x].l = l, tree[x].r = r;
    tree[x].len = 0;
    tree[x].sum = 0;
    if(l == r)
        return;
    int mid = (l + r) >> 1;
    build_tree(lson, l, mid);
    build_tree(rson, mid + 1, r);
    return;
}

void pushup(int x) {
    int l = tree[x].l, r = tree[x].r;
    if(tree[x].sum /* 也就是说被覆盖过 */ )
        tree[x].len = X[r + 1] - X[l];
//      更新长度        
    else
        tree[x].len = tree[lson].len + tree[rson].len;
//      合并儿子信息
}

void edit_tree(int x, ll L, ll R, int c) {
    int l = tree[x].l, r = tree[x].r;
//  注意,l、r和L、R的意义完全不同
//  l、r表示这个节点管辖的下标范围
//  而L、R则表示需要修改的真实区间
    if(X[r + 1] <= L || R <= X[l])
        return;
//  这里加等号的原因:
//  假设现在考虑 [2,5], [5,8] 两条线段,要修改 [1,5] 区间的sum
//  很明显,虽然5在这个区间内,[5,8] 却并不是我们希望修改的线段
//  所以总结一下,就加上了等号
    if(L <= X[l] && X[r + 1] <= R) {
        tree[x].sum += c;
        pushup(x);
        return;
    }
    edit_tree(lson, L, R, c);
    edit_tree(rson, L, R, c);
    pushup(x);
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lli %lli %lli %lli", &x1, &y1, &x2, &y2);
        X[2 * i - 1] = x1, X[2 * i] = x2;
        line[2 * i - 1] = (ScanLine) {x1, x2, y1, 1};
        line[2 * i] = (ScanLine) {x1, x2, y2, -1};
//      一条线段含两个端点,一个矩形的上下边都需要扫描线扫过
    }
    n <<= 1;
//  直接把 n <<= 1 方便操作
    sort(line + 1, line + n + 1);
    sort(X + 1, X + n + 1);
    int tot = unique(X + 1, X + n + 1) - X - 1;
//  去重最简单的方法:使用unique!(在<algorithm>库中)
    build_tree(1, 1, tot - 1);
//  为什么是 tot - 1 :
//  因为右端点的对应关系已经被篡改了嘛…
//  [1, tot - 1]描述的就是[X[1], X[tot]]
    ll ans = 0;
    for(int i = 1; i < n /* 最后一条边是不用管的 */ ; i++) {
        edit_tree(1, line[i].l, line[i].r, line[i].mark);
//      先把扫描线信息导入线段树
        ans += tree[1].len * (line[i + 1].h - line[i].h);
//      然后统计面积
    }
    printf("%lli", ans);
    return 0;
}

求周长
#include <iostream>
#include <stdio.h>
#include <algorithm>
#define lson (x << 1)
#define rson (x << 1 | 1)
using namespace std;
const int MAXN = 2e4;
int n, X[MAXN << 1];
int x1, y1, x2, y2, pre = 0; /* 先初始化为 0 */

struct ScanLine {
    int l, r, h, mark;
    if(h == rhs.h)
        return mark > rhs.mark;
    return h < rhs.h;
//      注意!这里是后来被 hack 掉以后加上去的
//      在此感谢 @leprechaun_kdl 指出问题
//      如果出现了两条高度相同的扫描线,也就是两矩形相邻
//      那么需要先扫底边再扫顶边,否则就会多算这条边
//      这个对面积并无影响但对周长并有影响
//      hack 数据:2 0 0 4 4 0 4 4 8 输出应为:24
} line[MAXN];

struct SegTree {
    int l, r, sum, len, c;
//  c表示区间线段条数
    bool lc, rc;
//  lc, rc分别表示左、右端点是否被覆盖
//  统计线段条数(tree[x].c)会用到
} tree[MAXN << 2];

void build_tree(int x, int l, int r) {
    tree[x].l = l, tree[x].r = r;
    tree[x].lc = tree[x].rc = false;
    tree[x].sum = tree[x].len = 0;
    tree[x].c = 0;
    if(l == r)
        return;
    int mid = (l + r) >> 1;
    build_tree(lson, l, mid);
    build_tree(rson, mid + 1, r);
}

void pushup(int x) {
    int l = tree[x].l, r = tree[x].r;
    if(tree[x].sum) {
        tree[x].len = X[r + 1] - X[l];
        tree[x].lc = tree[x].rc = true;
        tree[x].c = 1;
//      做好相应的标记
    }
    else {
        tree[x].len = tree[lson].len + tree[rson].len;
        tree[x].lc = tree[lson].lc, tree[x].rc = tree[rson].rc;
        tree[x].c = tree[lson].c + tree[rson].c;
//      如果左儿子左端点被覆盖,那么自己的左端点也肯定被覆盖;右儿子同理
        if(tree[lson].rc && tree[rson].lc)
            tree[x].c -= 1;
//      如果做儿子右端点和右儿子左端点都被覆盖,
//      那么中间就是连续的一段,所以要 -= 1
    }
}

void edit_tree(int x, int L, int R, int c) {
    int l = tree[x].l, r = tree[x].r;
    if(X[l] >= R || X[r + 1] <= L)
        return;
    if(L <= X[l] && X[r + 1] <= R) {
        tree[x].sum += c;
        pushup(x);
        return;
    }
    edit_tree(lson, L, R, c);
    edit_tree(rson, L, R, c);
    pushup(x);
}

ScanLine make_line(int l, int r, int h, int mark) {
    ScanLine res;
    res.l = l, res.r = r,
    res.h = h, res.mark = mark;
    return res;
}
//  POJ 不这样做就会CE,很难受

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
        line[i * 2 - 1] = make_line(x1, x2, y1, 1);
        line[i * 2] = make_line(x1, x2, y2, -1);
        X[i * 2 - 1] = x1, X[i * 2] = x2;
    }
    n <<= 1;
    sort(line + 1, line + n + 1);
    sort(X + 1, X + n + 1);
    int tot = unique(X + 1, X + n + 1) - X - 1;
    build_tree(1, 1, tot - 1);
    int res = 0;
    for(int i = 1; i < n; i++) {
        edit_tree(1, line[i].l, line[i].r, line[i].mark);
        res += abs(pre - tree[1].len);
        pre = tree[1].len;
//      统计横边
        res += 2 * tree[1].c * (line[i + 1].h - line[i].h);
//      统计纵边
    }
    res += line[n].r - line[n].l;
//  特判一下枚举不到的最后一条扫描线
    printf("%d", res);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值