【hdu】1828 Picture —— 扫描线求周长

原题链接

题目:

在这里插入图片描述
在这里插入图片描述

解题思路:

题目大意:给你n个边平行与坐标轴的矩形,矩形可能有重叠部分,问你所有矩形构成的图形的轮廓线多长(即外周长+内部中空的周长)

错误想法:做了之前的求矩形面积和的问题,那么自然会想到每一根扫描线都用线段树维护了有效的长度,那么每一次都加上那个长度,先从左往右跑一边,再从上往下跑一遍,答案不就出来了?这真的对吗?答案是:错误的!题目只要求出轮廓线长度,如果那样算,结果比答案大很多,因为算了很多不必要的长度。

正确做法
先考虑横边:
观察下图中扫描线扫过的横边,会发现它好像有一点规律,总结一下就是:横边总长度= ∑ 2 × 被 截 得 的 线 段 条 数 × 扫 过 的 宽 度 \sum2\times被截得的线段条数\times扫过的宽度 2×线×。被截得的线段条数,即非连续线段。
在这里插入图片描述
在考虑竖边:
事情就更加 恶心 有趣了,你会发现竖边总长度 = ∑ ∣ 上 次 截 得 的 总 长 − − 现 在 截 得 的 总 长 ∣ =\sum|上次截得的总长-−现在截得的总长∣ =

在这里插入图片描述
所以和面积并比起来,周长并中的线段树还要维护线段的条数。

注意点:
1.如果出现两条 x 坐标相等的扫描线,应该先处理入边,然后处理出边,因为这条公共边只需要处理一次!先处理入边,新的扫描线得长度不会变,那么答案不会增加,在处理出边时,新扫描线长度减少,答案增加相应长度,这样就计算了一遍;如果先处理出边,那么新的扫描线减少了,答案增加相应长度,再处理入边时,新扫描线的长度又增加了,答案又增加了一遍长度,重复计算了!所以,扫描线的排序有所不同!
2.当计算线段条数时,应该考虑中间合并的情况,所以要增加两个变量 lc,rc ,代表该区间的左端点是否覆盖,右端点是否覆盖。当合并时,如果左子区间的右端点被覆盖,右子区间的左端点被覆盖,那么父节点的线段条数应该减1。
3.别忘记最后答案加上最后一条扫描线的长度。

代码:

#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e4 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
int n;
ll ans;
struct line{
	int x, y1, y2;
	int state;
	bool operator<(line b) const {
		if(x==b.x)	return state>b.state;//重合时,先加边,再减边 
		return x<b.x;
	}
};
int ls[MAXN];//去重排序后的y坐标 
int alls[MAXN];//所有y坐标 
int lscnt;

int ask(int x){
	return lower_bound(ls+1, ls+1+lscnt, x) - ls;
}

struct node1{
	int l, r;//线段的编号 
	int len;//该区间覆盖线段的长度 
	int cover;//被全部覆盖的次数 
	bool lc,rc;//左端点是否被覆盖 右端点是否被覆盖
	int  cnt;//区间内线段的个数 
}tree[MAXN << 3];

void pushup(int rt){
	if(tree[rt].cover)
	{
		tree[rt].len=ls[tree[rt].r+1]-ls[tree[rt].l];
		tree[rt].lc=true;
		tree[rt].rc=true;
		tree[rt].cnt=1;
	}	
	else
	{
		tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len;
		tree[rt].lc=tree[rt<<1].lc;
		tree[rt].rc=tree[rt<<1|1].rc;
		tree[rt].cnt=tree[rt<<1].cnt+tree[rt<<1|1].cnt;
		if(tree[rt<<1].rc && tree[rt<<1|1].lc)//合并时中间连续的 
			tree[rt].cnt-=1;
	}	
}

void build(int l, int r, int rt){
	tree[rt].l=l;
	tree[rt].r=r;
	tree[rt].len=tree[rt].cover=tree[rt].cnt=0;
	tree[rt].lc=tree[rt].rc=false;
	if(l == r)	return ;
	int m = (l + r) >> 1;
	build(l, m, rt << 1);
	build(m+1, r, rt << 1 | 1);
}

void update(int L, int R, int state, int rt){
	if(L <= tree[rt].l && tree[rt].r <= R){
		tree[rt].cover+=state;
		pushup(rt);
		return ;
	}
	int m = (tree[rt].l + tree[rt].r) >> 1;
	if(L <= m)
		update(L, R, state, rt << 1);
	if(m < R)
		update(L, R, state, rt << 1 | 1);
	pushup(rt);
}

void solve(){
    vector<line> v;
    int cnt=0;
    for(int i = 1; i <= n; i++){
    	int x1, y1, x2, y2;
    	cin >> x1 >> y1 >> x2 >> y2;
    	v.pb(line{x1, y1, y2, 1});
    	v.pb(line{x2, y1, y2, -1});
    	alls[++cnt] = y1;
    	alls[++cnt] = y2;
    }
    sort(alls+1, alls+1+cnt);
	lscnt = 0;
    alls[0] = -inf;
    for(int i = 1; i <= cnt; i++)
    	if(alls[i] != alls[i-1])
    		ls[++lscnt] = alls[i];
    sort(v.begin(), v.end());
    build(1, lscnt-1, 1);
    ans = 0;
	int pre=0;
    for(int i = 0; i < 2*n-1; i++){
    	update(ask(v[i].y1), ask(v[i].y2)-1, v[i].state, 1);
    	ans+=abs(tree[1].len-pre);
    	ans+=(v[i+1].x-v[i].x)*2*tree[1].cnt;
    	pre=tree[1].len;
    }
    ans+=v[2*n-1].y2-v[2*n-1].y1;//最后一条边的长度 
}

int main()
{
	qc;
	while(cin>>n) 
	{
		solve();
		printf("%lld\n",ans);
	    memset(alls,0,sizeof(alls));
	    memset(ls,0,sizeof(ls));
	    memset(tree,0,sizeof(tree));
	}
	return 0;
}

碎碎念:

这规律,妙啊!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值