hdu 1828 Picture

本文介绍了一种使用线段树和扫描线法计算多个矩形并集周长的算法。通过将矩形的周长分解为横纵两部分,并利用线段树维护扫描线上的边数和长度变化,实现了高效计算。

Problem

acm.hdu.edu.cn/showproblem.php?pid=1828

Reference

线段树辅助——扫描线法计算矩形周长并(轮廓线)
矩形面积并、矩形面积交、矩形周长并(线段树、扫描线总结)

Meaning

给出 n 个矩形,求它们并出来的图形的周长(内、外的边都算)

Analysis

把周长分开横线和竖线。以从下往上扫为例。
算面积并类似,用 len 数组记录在扫描线上的边(即横向的边)的总长,每一次横向边的增益是:|今次的横边总长 - 上次的横边总长|(初始的横边总长为零),因为每次扫到一条横边,要么是增加区间,要么是删除区间,而无论是增加的还是删除的,都是图形的边,都要算进周长内,上述差值的绝对值,就是增加或减少的长度。
对于竖边,在每一个(被相邻横线分开的)区间里,都有若干条等长的竖边,长度就是那两条分割线之间的间隔,所以需要多做的就是统计竖线的数目。数线的数目就是线段数目的两倍(一条线段两个端点),于是可以转为维护当前在扫描线上的线段总数。

Code

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000;

struct segment
{
    int l, r, h, v;

    segment() {}

    segment(int _l, int _r, int _h, int _v) :
        l(_l), r(_r), h(_h), v(_v) {}

    bool operator < (const segment &rhs) const
    {
        return h < rhs.h;
    }
} s[N<<1|1];

int dsc[N<<1]; // 离散化数组
int len[N<<3] = {0}; // 区间有效总长(在扫描线上的边的并)
int seg[N<<3] = {0}; // 区间的线段总数
int cvr[N<<3] = {0}; // 区间是否被完全覆盖
bool lp[N<<3] = {false}; // 区间左端点是否被覆盖
bool rp[N<<3] = {false}; // 区间右端点是否被覆盖

void pushup(int l, int r, int rt)
{
    if(cvr[rt]) // 被完全覆盖
    {
        len[rt] = dsc[r] - dsc[l];
        seg[rt] = lp[rt] = rp[rt] = 1;
    }
    else if(l + 1 == r) // 已是最小区间,无子区间
        len[rt] = seg[rt] = lp[rt] = rp[rt] = 0;
    else
    {
        len[rt] = len[rt<<1] + len[rt<<1|1];
        lp[rt] = lp[rt<<1]; // 与左子区间共左端点
        rp[rt] = rp[rt<<1|1]; // 与右子区间共右端点
        seg[rt] = seg[rt<<1] + seg[rt<<1|1];
        // 如果左子区间的右端点
        // 和右子区间的左端点
        // 同时被覆盖
        // 说明中间被覆盖的线段是连续的(跨了两个子区间)
        // 中间是没有断点的
        if(rp[rt<<1] && lp[rt<<1|1])
            --seg[rt];
    }
}

void update(int ul, int ur, int v, int l, int r, int rt)
{
    if(ul <= l && r <= ur)
    {
        cvr[rt] += v;
        pushup(l, r, rt);
        return;
    }
    int m = l + r >> 1;
    if(ul < m)
        update(ul, ur, v, l, m, rt<<1);
    if(ur > m)
        update(ul, ur, v, m, r, rt<<1|1);
    pushup(l, r, rt);
}

int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        for(int i = 0, l, r, u, d; i < n; ++i)
        {
            scanf("%d%d%d%d", &l, &d, &r, &u);
            s[i] = segment(l, r, d, 1);
            s[i+n] = segment(l, r, u, -1);
            dsc[i] = l;
            dsc[i+n] = r;
        }
        n <<= 1;
        sort(s, s + n);
        sort(dsc, dsc + n);
        int m = unique(dsc, dsc + n) - dsc;
        /* 标记最终都会被清除掉
         * 故这些清零操作可以省去
         * memset(cvr, 0, sizeof cvr);
         * memset(seg, 0, sizeof seg);
         * memset(len, 0, sizeof len);
         * memset(lp, false, sizeof lp);
         * memset(rp, false, sizeof rp);
         */
        int ans = 0;
        // 因为所有边都被遍历(i 遍历到 n - 1)
        // 所有线段的数据都会被用来 update
        // 所以上面那些标记才会被完全清掉
        // (而算面积并、交时并不是这样)
        for(int i = 0, l, r, pre = 0; i < n; ++i) // 而不是 i < n - 1
        {
            l = lower_bound(dsc, dsc + m, s[i].l) - dsc;
            r = lower_bound(dsc, dsc + m, s[i].r) - dsc;
            update(l, r, s[i].v, 0, m - 1, 1);
            // 竖线段数 = 横线段数 * 2
            // 竖线段长 = 横线段间距
            ans += (s[i+1].h - s[i].h) * seg[1] << 1;
            // 横线段增量 = 新增的横线段长 + 减除的横线段长
            // 即:上次总横线段长 与 今次总横线段长 的差别
            ans += abs(len[1] - pre);
            // 更新“上次”横线段长
            pre = len[1];
        }
        printf("%d\n", ans);
    }
    return 0;
}
第五届广西省大学生程序设计竞赛K Kirby's challenge(AC代码) 分数 300 作者 Colin 单位 杭州电子科技大学 Description Recently, Colin bought a Switch for Eva. And they are playing "Kirby and the Forgotten Land". In a challenge mission, Kirby is in a 4×n grid. The row of it is numbered from 1 to 4, and the column of it is numbered from 1 to n. There are many keys in this grid. Let a x,y ​ represent the status of cell (x,y). If a x,y ​ =1, there is a key in (x,y). If a x,y ​ =0, there is no key in (x,y). Kirby starts at (1,1), and should reach (4,n). Moreover, Kirby must collect all the keys in the grid to open the door in (4,n). Kirby will collect the key at (x,y) when Kirby reach (x,y). Of course, Kirby will collect the key at (1,1) at the beginning. In a second, Kirby can move from (x,y) to (x+1,y),(x,y+1),(x−1,y). Or Kirby can stay at (x,y) and throw a returnable flying weapon(boomerang) to collect keys in the flying path. Kirby has two ways to throw the weapon. As the picture shows: image-20220604213457062.png The flying path is represented as the grey cells, so keys in the grey cells can be collected by the weapon. In a second, Kirby can only choose one way to throw the weapon, but Kirby can throw the weapon multiple times at (x,y) if necessary. Notice: Kirby can't get off the grid, but the weapon can fly outside the grid and keep the flying path. Please write a program to help Colin and Eva find the shortest time to complete the challenge mission, so that they can get more rewards. Input The first line contains one integer n (1≤n≤100). In the next 4 lines, the x-th line contains n integers a x,1 ​ ,a x,2 ​ ,⋯,a x,n ​ (0≤a x,y ​ ≤1). Output Print one integer representing the minimum number of seconds required to complete the challenge mission. Sample input 1 5 1 1 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 1 output 1 8 The best solution is: Spend 1 second to throw the weapon in the second way at (1,1), and spend 7 seconds to reach (4,5). 代码长度限制 16 KB 时间限制 1000 ms 内存限制 512 MB 栈限制 131072 KB
最新发布
08-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值