bzoj5465 [APIO 2018] 选圆圈 kd树

8 篇文章 0 订阅

Description


在平面上,有 n 个圆,记为 c_1, c_2, \ldots, c_n 。我们尝试对这些圆运行这个算法:

  1. 找到这些圆中半径最大的。如果有多个半径最大的圆,选择编号最小的。记为 c_i 。
  2. 删除 ci 及与其有交集的所有圆。两个圆有交集当且仅当平面上存在一个点,这个点同时在这两个圆的圆周上或圆内。
    (如果平面上存在一个点被这两个圆所包含,我们称这两个圆有交集。一个点被一个圆包含当且仅当它位于圆内或圆周上。)
  3. 重复上面两个步骤直到所有的圆都被删除。
    在这里插入图片描述
    当 ci 被删除时,若循环中第1步选择的圆是 cj ,我们说 ci 被 cj 删除。对于每个圆,求出它是被哪一个圆删除的。

-1e9<=xi,yi<=1e9, 1<=ri<=1e9
1<=n<=3e5

Solution


我是一只咸鱼~~写这题只是为了写kd树
去年考场上按subtask打了线段树+枚举的又臭又长的暴力,也只有19分,真的菜

我们用矩形框住一个圆,然后对矩形建kd树。查询交的时候判掉相离的矩形然后暴力走就可以了
为了不被卡,可以考虑把坐标系旋转若干角度,比如我这里转了20°。实际操作因为是ioi赛制因此可以调参(雾
似乎没啥好说的啊

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define sqr(x) ((x)*(x))

const int N=600005;
const double eps=1e-3;
const double pi=acos(-1);
const double cosa=cos(pi/9.0);
const double sina=sin(pi/9.0);

int wjp;

struct Cir {double x,y,r; int id;} c[N];

struct treeNode {
	int son[2],id;
	double mx[2],mn[2],p[2],r;
	bool operator <(const treeNode &b) const {
		return p[wjp]<b.p[wjp];
	}
	void init(int idd) {
		id=idd;
		mx[0]=mn[0]=p[0]=c[idd].x;
		mx[1]=mn[1]=p[1]=c[idd].y;
		r=c[idd].r;
	}
} t[N],Q;

int ans[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void Max(double &x,double v) {
	(x<v)?(x=v):0;
}

void Min(double &x,double v) {
	(x>v)?(x=v):0;
}

int push_up(int x) {
	t[x].mx[0]=t[x].mn[0]=t[x].p[0];
	t[x].mx[1]=t[x].mn[1]=t[x].p[1];
	int ls=t[x].son[0],rs=t[x].son[1];
	if (ls) {
		Max(t[x].mx[0],t[ls].mx[0]); Max(t[x].mx[1],t[ls].mx[1]);
		Min(t[x].mn[0],t[ls].mn[0]); Min(t[x].mn[1],t[ls].mn[1]);
	}
	if (rs) {
		Max(t[x].mx[0],t[rs].mx[0]); Max(t[x].mx[1],t[rs].mx[1]);
		Min(t[x].mn[0],t[rs].mn[0]); Min(t[x].mn[1],t[rs].mn[1]);
	}
	return x;
}

int build(int l,int r,int R) {
	int mid=(l+r)>>1;
	wjp=R; std:: nth_element(t+l,t+mid,t+r+1);
	if (l<mid) t[mid].son[0]=build(l,mid-1,!R);
	if (mid<r) t[mid].son[1]=build(mid+1,r,!R);
	return push_up(mid);
}

bool check(int x) {
	if (Q.p[0]+Q.r+Q.r+eps<t[x].mn[0]) return 1;
	if (Q.p[1]+Q.r+Q.r+eps<t[x].mn[1]) return 1;
	if (Q.p[0]-Q.r-Q.r>t[x].mx[0]+eps) return 1;
	if (Q.p[1]-Q.r-Q.r>t[x].mx[1]+eps) return 1;
	return 0;
}

bool in(int x) {
	return sqr(Q.p[0]-t[x].p[0])+sqr(Q.p[1]-t[x].p[1])-eps<=sqr(t[x].r+Q.r);
}

void query(int x,int now) {
	if (!x||check(x)) return ;
	if (!ans[t[x].id]&&in(x)) ans[t[x].id]=now;
	query(t[x].son[0],now);
	query(t[x].son[1],now);
}

bool cmp(Cir a,Cir b) {
	return (a.r==b.r)?(a.id<b.id):(a.r>b.r);
}

int main(void) {
	freopen("data.in","r",stdin);
	int n=read();
	rep(i,1,n) {
		double x=read(),y=read();
		c[i].x=x*cosa-y*sina;
		c[i].y=x*sina+y*cosa;
		// c[i].x=x,c[i].y=y;
		c[i].r=read(),c[i].id=i;
		t[i].init(i);
	}
	int rt=build(1,n,0);
	std:: sort(c+1,c+n+1,cmp);
	rep(i,1,n) if (!ans[c[i].id]) {
		ans[c[i].id]=c[i].id;
		Q.p[0]=c[i].x;
		Q.p[1]=c[i].y;
		Q.r=c[i].r;
		query(rt,c[i].id);
	}
	rep(i,1,n) printf("%d ", ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值