【Gym-102483A | NWERC 2018】Access Points(转化/单调栈)

Gym - 102483A

题意

平面内有n个点 P 1 , P 2 . . . P n P_1,P_2...P_n P1,P2...Pn,找到一个包含n个点的序列 Q 1 , Q 2 . . . Q n Q_1,Q_2...Q_n Q1,Q2...Qn,满足 Q i Q_i Qi 的横纵坐标都大于等于 Q i − 1 Q_{i-1} Qi1(即 Q i Q_i Qi 位于 Q i − 1 Q_{i-1} Qi1 的左上方),并使 ∑ i = 1 n ∣ ∣ Q i − P i ∣ ∣ 2 \sum^{n}_{i=1} ||Q_i-P_i||^2 i=1nQiPi2 最小,输出该式的最小值。

题解

首先观察式子可以得出: ∥ P i − Q i ∥ 2 = ( x ( P i ) − x ( Q i ) ) 2 + ( y ( P i ) − y ( Q i ) ) 2 ∥P_i − Q_i∥^2 = (x(P_i) − x(Q_i))^2 + (y(P_i) − y(Q_i))^2 PiQi2=(x(Pi)x(Qi))2+(y(Pi)y(Qi))2,因此对于横坐标和纵坐标来说是两个独立的子问题,分别求解相加即可得到答案。
于是问题转化成了:已知序列 A 1 , A 2 . . . A n A_1,A_2...A_n A1,A2...An,找到一个单调递增的序列 X 1 , X 2 . . . X n X_1,X_2...X_n X1,X2...Xn,使得 ∑ i = 1 n ( A i − X i ) 2 \sum^{n}_{i=1} (A_i-X_i)^2 i=1n(AiXi)2 最小,求这个最小值。
通过观察我们可以得到两条性质:假如所有X都相同,那么当X是A的平均数时,整个式子的值最小(二次函数的极值问题,展开求解即可);假如A是单调递减的,那么最优解的所有X取值一定相同。这样就可以得到一种做法:将A分割成多个下降的子串,每个子串取平均值并取代原串,重复该操作直到A是单调递增的。在最坏情况下,这种做法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),需要优化。能不能在一段子串的平均值算出来后立刻就与前面平均值较大的子串合并呢?答案是可以的。考虑使用单调栈,栈中存储了子串的平均值和长度,每次算好平均值就与栈顶比较,若栈顶较大就不断弹出并与当前子串合并。由于以每个位置为结尾子串最多入栈一次,因此时间复杂度是 O ( n ) O(n) O(n)
代码写得比较简单,是由于将A的每一个点都分割为一个子串,且栈中记录的是和与长度。

#include
using namespace std;
typedef long long ll;
const int N=12e4;

int x[N],y[N];
double solve(int *x,int n){
	vector<pair<ll,int>>q;
	for(int i=0;i<n;i++){
		ll s=x[i];int l=1;
		while(q.size() && q.back().first*l >= s*q.back().second){
			l+=q.back().second;
			s+=q.back().first;
			q.pop_back();
		}
		q.push_back(make_pair(s,l));
	}
	double ans=0;
	int i=0;
	for(auto u:q){
		double a=1.0*u.first/u.second;
		for(int j=0;j<u.second;j++){
			ans+=(a-x[i+j])*(a-x[i+j]);
		}
		i+=u.second;
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>x[i]>>y[i];
	}
	printf("%.9f",solve(x,n)+solve(y,n));
	return 0;
}

你可能感兴趣的:(单调栈)