Codeforces Gym100543B 计算几何 凸包 线段树 二分/三分 卡常

解决一个复杂的几何问题,通过构建线段树并利用上凸壳的特性,实现高效的查询算法。

原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543B.html

题目传送门 - CF-Gym100543B

题意

  给定一个折线图,对于每一条折线,问沿着这条折线往右看第一个看到的线段的编号(如果视线恰好看到上端点,则当没看见)

  放张图片助于理解:

  

  折线图用 $n$ 个点来描述。

  $n\leq 100000,\ \ \ \ 坐标范围:(x,y)|0\leq x,y\leq 10^9$

题解

  这题好妙啊。

  首先一个结论:如果射线与一段区间的点形成的上凸壳相交,那么他一定与这段区间内的折线段相交。

  我们只需要建个线段树,所有节点上建一个当前节点所表示的区间内的点构成的上凸壳,然后每次 $O(\log^2 n)$ 询问即可。

  如何 $O(\log^2 n)$ 询问?

  首先,线段树一只 $\log$ 。

  我们需要支持的是一只 $\log$ 判断射线是否与上凸壳相交。

  显然原线段与上凸壳的点的叉积是一个单峰函数。(根据叉积的定义,平行四边形的低不变,高为单峰函数,故面积也为单峰函数)

  于是显然可以三分搞定。

  但是被卡常数了。

  于是 foreverpiano 告诉了我一种巧妙的二分做法。

  (这里求叉积的点依次是线段左侧点,线段右侧点,当前点)

  对于每一次的 $mid$ ,我们看一看 原线段与凸壳上面的第 $mid$ 和 $mid+1$ 个点的叉积大小,分别记为 $v1$ 和 $v2$。

  如果 $v1>v2$ 则令 $R=mid-1$ 否则令 $L=mid+1$ 。

  注意一旦有 $v1>0$ 或者 $v2>0$ 就可以判断一定相交了。如果这个时候不return,则可能会有漏算。

  然后区间极小的时候暴力判。

  然后常数小了好多。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100005;
int T,n;
struct Point{
	int x,y;
	Point(){}
	Point(int _x,int _y){
		x=_x,y=_y;
	}
}p[N],P[N];
vector <int> s[N<<3];
LL cross(Point a,Point b,Point c){
	return 1LL*(b.x-a.x)*(c.y-a.y)-1LL*(c.x-a.x)*(b.y-a.y);
}
int read(){
	char ch=getchar();
	int x=0;
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return x;
}
int st[N],top;
void Get_convex(vector <int> &s,int L,int R){
	st[top=1]=R,st[++top]=R-1;
	for (int i=R-2;i>=L;i--){
		while (top>1&&cross(p[st[top-1]],p[st[top]],p[i])<=0)
			top--;
		st[++top]=i;
	}
	s.clear();
	for (int i=top;i>0;i--)
		s.push_back(st[i]);
}
void build(int rt,int L,int R){
	if (L==R){
		s[rt].clear();
		s[rt].push_back(L);
		return;
	}
	Get_convex(s[rt],L,R);
	int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
	build(ls,L,mid);
	build(rs,mid+1,R);
}
int Query(Point a,Point b,vector <int> &s){
	int L=0,R=s.size()-1,mid;
	while (L+3<=R){
		mid=(L+R)>>1;
		LL x=cross(a,b,p[s[mid]]),y=cross(a,b,p[s[mid+1]]);
		if (x>0||y>0)
			return 1;
		if (x>y)
			R=mid-1;
		else
			L=mid+1;
	}
	int now=-1;
	for (int i=L;i<=R;i++)
		if (cross(a,b,p[s[i]])>0)
			return 1;
	return 0;
}
int Query(int rt,int L,int R,int xL,int xR){
	if (xL>xR||L>xR||R<xL||!Query(p[xL-2],p[xL-1],s[rt]))
		return 0;
	if (L==R)
		return L-1;
	int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
	int now=Query(ls,L,mid,xL,xR);
	return now?now:Query(rs,mid+1,R,xL,xR);
}
int main(){
	T=read();
	while (T--){
		n=read();
		for (int i=1;i<=n;i++)
			p[i].x=read(),p[i].y=read();
		build(1,1,n);
		for (int i=1;i<n;i++){
			cout << Query(1,1,n,i+2,n);
			if (i<n-1)
				putchar(' ');
		}
		puts("");
	}
	return 0;
}

  

转载于:https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543B.html

Codeforces Gym 101630 是一场编程竞赛,通包含多个算法挑战问题。这些问题往往涉及数据结构算法设计、数学建模等多个方面,旨在测试参赛者的编程能力和解决问题的能力。 以下是一些可能出现在 Codeforces Gym 101630 中的题目类型及解决方案概述: ### 题目类型 1. **动态规划(DP)** 动态规划是编程竞赛中见的题型之一。问题通要求找到某种最优解,例如最小路径和、最长递增子序列等。解决这类问题的关键在于状态定义和转移方程的设计[^1]。 2. **图论** 图论问题包括最短路径、最小生成树、网络流等。例如,Dijkstra 算法用于求解单源最短路径问题,而 Kruskal 或 Prim 算法用于最小生成树问题[^1]。 3. **字符串处理** 字符串问题可能涉及模式匹配、后缀数组、自动机等高级技巧。KMP 算法和 Trie 树是解决此类问题的用工具[^1]。 4. **数论组合数学** 这类问题通需要对质数、模运算、排列组合等有深入的理解。例如,快速幂算法可以用来高效计算大数的模幂运算[^1]。 5. **几何** 几何问题可能涉及点、线、多边形的计算,如判断点是否在多边形内部、计算两个圆的交点等。向量运算和坐标变换是解决几何问题的基础[^1]。 ### 解决方案示例 #### 示例问题:动态规划 - 最长递增子序列 ```python def longest_increasing_subsequence(nums): if not nums: return 0 dp = [1] * len(nums) for i in range(len(nums)): for j in range(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) # 示例输入 nums = [10, 9, 2, 5, 3, 7, 101, 18] print(longest_increasing_subsequence(nums)) # 输出: 4 ``` #### 示例问题:图论 - Dijkstra 算法 ```python import heapq def dijkstra(graph, start): distances = {node: float('infinity') for node in graph} distances[start] = 0 priority_queue = [(0, start)] while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) if current_distance > distances[current_node]: continue for neighbor, weight in graph[current_node].items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances # 示例输入 graph = { 'A': {'B': 1, 'C': 4}, 'B': {'A': 1, 'C': 2, 'D': 5}, 'C': {'A': 4, 'B': 2, 'D': 1}, 'D': {'B': 5, 'C': 1} } start = 'A' print(dijkstra(graph, start)) # 输出: {'A': 0, 'B': 1, 'C': 3, 'D': 4} ``` #### 示例问题:字符串处理 - KMP 算法 ```python def kmp_failure_function(pattern): m = len(pattern) lps = [0] * m length = 0 # length of the previous longest prefix suffix i = 1 while i < m: if pattern[i] == pattern[length]: length += 1 lps[i] = length i += 1 else: if length != 0: length = lps[length - 1] else: lps[i] = 0 i += 1 return lps def kmp_search(text, pattern): n = len(text) m = len(pattern) lps = kmp_failure_function(pattern) i = 0 # index for text j = 0 # index for pattern while i < n: if pattern[j] == text[i]: i += 1 j += 1 if j == m: print("Pattern found at index", i - j) j = lps[j - 1] elif i < n and pattern[j] != text[i]: if j != 0: j = lps[j - 1] else: i += 1 # 示例输入 text = "ABABDABACDABABCABAB" pattern = "ABABCABAB" kmp_search(text, pattern) # 输出: Pattern found at index 10 ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值