P8212 [THUPC2022 初赛] 喵喵花園 题解

是我喜欢的计算几何!赛场上一边吃午饭一边写完的(

首先我们知道这 KK 棵圣诞树均匀分割了整个凸多边形的边长,那么相邻两棵圣诞树在凸多边形边上的距离就是一定的(设为 disdis)。我们选择一棵树观察,将其向顺时针方向挪动 disdis,发现对于每一棵树,其后面一棵树来到了它先前所在的位置,整体也就回到了挪动前的状态。

我们定义 跳边 代表树挪到一条边的端点,然后前往下一条边的瞬间,那么由于所有树移动的距离恰好覆盖整个凸多边形的周长,发生的 跳边 次数和凸多边形拐角数目一样,也就是 NN。

考虑模拟整个过程,钦定一棵树,一开始在凸多边形的某一个端点,然后向右挪 disdis。期间我们可以求出其它所有点的位置,由此推出所有 跳边 的时间点。由于两次 跳边 之间,每个点都在一个线段之间移动,所以我们尝试用函数表示出这段时间的凸多边形面积,并求出最小值。

我们定义这 KK 棵树所在位置为 t_1, t_2, ..., t_Kt1​,t2​,...,tK​,这 NN 条边的长度为 s_1, s_2, ..., s_Ns1​,s2​,...,sN​,其中 s_1s1​ 代表的边连接 p_1p1​ 和 p_2p2​,依次类推,同时默认 p_{N+1}=p_1pN+1​=p1​。

在这个时间段的开始,每个点在凸多边形的周长上和 p_1p1​ 的距离(p_1p1​ 顺时针到达该点的距离)是确定的。我们可以按顺序枚举每个点,然后使用指针维护当前点所在的边。

假设第 ii 个点落在了第 jj 条边上,和 p_jpj​ 的距离为 dd。那么我们考虑将这个点的坐标借助时间表示出来。我们不妨假设时间为 xx,那么前面的距离将会扩大到 d+xd+x,根据向量的知识,我们可以求出 \overrightarrow{p_jp_{j+1}}pj​pj+1​​ 的单位向量于距离相加,加上初始点 p_jpj​,即:

t_i = p_j + \dfrac{\overrightarrow{p_jp_{j+1}}}{|\overrightarrow{p_jp_{j+1}}|} \times (d + x)ti​=pj​+∣pj​pj+1​​∣pj​pj+1​​​×(d+x)

我们就成功使用时间表示了每个点的位置。

然后我们就可以使用叉积算出凸多边形的面积(具体而言,选择一个点向其余所有非相邻点连边,将凸多边形拆成若干个三角形,而向量叉积的结果的绝对值恰为其围成三角形面积的两倍),最终得到一个二次函数。那么我们只需要考虑这个时间段内二次函数的最小值就能解决问题。

最后把所有时间段的答案统计起来就完成了,复杂度为 O(NK + N^2)O(NK+N2)。如果在求出 跳边 时间点的同时存储对应点,就可以省去重新获取所有点所在边的位置的时间复杂度,使总复杂度降为 O(NK)O(NK)。

代码中 KBs 维护了 kx + bkx+b。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;
struct KBs{
	double k, b;
	KBs(double k=0, double b=0)
		:k(k), b(b){}
	KBs operator + (const KBs x) const{
		return KBs(k + x.k, b + x.b);
	}
	KBs operator - (const KBs x) const{
		return KBs(k - x.k, b - x.b);
	}
	KBs operator * (const double p) const{
		return KBs(k * p, b * p);
	}
};
struct Point{
	KBs x, y;
	Point(KBs x = 0, KBs y = 0)
		:x(x), y(y){}
	Point operator + (const Point& p)const{
		return Point(x + p.x, y + p.y);
	}
	Point operator - (const Point& p)const{
		return Point(x - p.x, y - p.y);
	}
	Point operator * (const double k)const{
		return Point(x * k, y * k);
	}
	Point operator * (const KBs k)const{
		return Point(k * x.b, k * y.b);
	}
};
double sqr(double x){
	return x * x;
}
struct Segment{
	Point st, ed;
	Segment(Point st = Point(KBs(), KBs()), Point ed = Point(KBs(), KBs()))
		:st(st), ed(ed){}
	double len(){
		return sqrt(sqr(st.x.b - ed.x.b) + sqr(st.y.b - ed.y.b));
	}
	Point at(KBs k){
		return st + (ed - st) * k;
	}
};
struct Function{
	double a, b, c;
	Function(double a=0, double b=0, double c=0)
		:a(a), b(b), c(c){}
	Function operator + (const Function& f)const{
		return Function(a + f.a, b + f.b, c + f.c);
	}
	Function operator - (const Function& f)const{
		return Function(a - f.a, b - f.b, c - f.c);
	}
	double at(double x){
		return a * x * x + b * x + c;
	}
	double minn(double l, double r){
		if(a == 0)
			return min(at(l), at(r));
		// return 0.0;
		double mn = -1.0 * b / (2 * a);
		if(l <= mn && mn <= r)
			return min({at(l), at(r), at(mn)});
		return min(at(l), at(r));
	}
};
Point ps[1010];
Segment segs[1010];
int N, K;
int curr[1010];
double lens[1010];
double lenfnt[1010];
vector<pair<double, int> > vec;

Function KBsCross(KBs x, KBs y){
	return Function(x.k * y.k, x.k * y.b + x.b * y.k, x.b * y.b);
}
Function pointCross(Point x, Point y){
	return KBsCross(x.x, y.y) - KBsCross(x.y, y.x);
}

int main(){
	scanf("%d%d", &N, &K);
	for(int i=N; i>=1; i--)
		scanf("%lf %lf", &ps[i].x.b, &ps[i].y.b);
	ps[N+1] = ps[1];
	double C = 0;
	for(int i=1; i<=N; i++)
		segs[i] = Segment(ps[i], ps[i+1]), C += lens[i] = segs[i].len();
	segs[N+1] = segs[1];
	C /= K;
	double las = 0;
	int m = 1;
	curr[1] = 0;
	for(int i=1; i<=N; i++){
		las += lens[i];
		lenfnt[i] = lenfnt[i-1] + lens[i];
		while(las >= C && m != K)
			las -= C, curr[++m] = i;
		if(i == N)
			m = 1;
		else
			vec.emplace_back(las, m);
	}
	lens[N+1] = lens[1];
	vec.emplace_back(0, 1);
	vec.emplace_back(C, 1);
	sort(vec.begin(), vec.end());
	double ans = 1e18;
	for(int i=0; i<(int)vec.size()-1; i++){
		pair<double, int> pr = vec[i];
		curr[pr.second] ++;
		double L = pr.first, R = vec[i+1].first;
		vector<Point> plist(K + 2);
		for(int j=1; j<=K; j++){
			int id = curr[j];
			double y = (j-1) * C - lenfnt[id-1];
			KBs num(1.0 / lens[id], y / lens[id]);
			plist[j] = segs[id].at(num);
		}
		Function f;
		for(int i=1; i<=K; i++){
			int j = i % K + 1;
			f = f + pointCross(plist[i], plist[j]);
		}
		if(f.at(L) < 0) // 建议加上这个检查,防止凸多边形叉出来面积是个负数
			f.a *= -1, f.b *= -1, f.c *= -1;
		ans = min(ans, f.minn(L, R));
	}
	printf("%.12f", ans / 2);
	return 0;
}

 5 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值