最小化圆上关键点到同一点距离和
|Word count:675|Reading time:2min|Post View:
题解
圆上有$n$个关键点,最小化他们到同一点的距离和。
考虑在圆上顺时针移动所求点,在移动的过程中一些关键点到该点距离增加,一些关键点到该点距离减少,一些关键点距离先增加再减少或是先减少再增加。考虑以在移动过程中每个关键点 增加/减少 的分界处(显然这些分界处即为每个关键点在圆上相对的点和自己本身)为单位枚举移动所求点,那么在移动过程中关键点到该点的距离要么增加,要么减少,只需要维护这两种情况分别有多少个点即可。(显然所求点一定在分界处上,因为在分界处之间的距离和变化单调,即分界处之间的距离和一定小于两边某一个分界处的距离和)
把$n$个关键点移动到圆上$n$等分位置也可以用相同的方法,先将点按极角排序,默认第$i$个点移动到第$i$个$n$等分位置即可(即$a_i-=360i/n$,在此次操作后第$i$个等分位置移动到了第$0$个等分位置,$a_i$移动到了$a_i-360i/n$)
正确的做法是将第$i$个关键点跟第$i$个等分点绑定,然后考虑移动一起所有等分点的过程。
以下是把$n$个关键点移动到圆上$n$等分位置,最小化距离和的算法。
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5+5;
int n; ll a[N],b[N],q[N*2]; multiset<pair<ll,int>> s;
int main(){ cin>>n; ll r=36000000ll*n; ll dec=100000ll*n; for(int i=1;i<=n;++i){ double b; cin>>b; a[i]=b*dec+0.6; } sort(a+1,a+n+1); ll tot=0; for(int i=1;i<=n;++i){ ll s=(a[i]-36000000ll*(i-1)+r)%r; b[i]=s; } sort(b+1,b+n+1); for(int i=n;i>=1;--i)b[i]=b[i]-b[1]; for(int i=1;i<=n;++i)tot+=min(b[i],r-b[i]); ll cnt1=0,cnt2=0; ll ans=tot; for(int i=1;i<=n;++i){ ll x1=b[i]; ll x2=(x1+r/2)%r; if(x1<r/2){ cnt1++; s.insert({x1,1}); s.insert({x2,-1}); } else{ cnt2++; s.insert({x1,1}); s.insert({x2,-1}); } q[++q[0]]=x1; q[++q[0]]=x2; } sort(q+1,q+q[0]+1); q[++q[0]]=r; ll last=0; for(auto p:s){ tot-=cnt1*(p.first-last); tot+=cnt2*(p.first-last); last=p.first; ans=min(ans,tot); if(p.second==1)cnt1--,cnt2++; else cnt1++,cnt2--; } double res=1.*ans/dec; res=res/180*acos(-1); printf("%.10lf\n",res); }
|