一眼看上去就应该能用半平面交去做。
首先考虑怎么求可能得第1名的人:每个人的函数为直线,就是在所有人的半平面交中的上边界者即可获得第一名,这个可以单调队列求解。
再考虑如何求可能得第2名的人:满足2个条件:1、在去掉可能得第1名的人后可以拿第1,这个跳转到上面的过程;2、至多同时被1个能拿第一名的人超越,这个应该在半平面上做一个区间覆盖,可以线段树或者二分求解,我懒得不会写线段树就只写了二分。
第3~m名,可以仿照第2名的过程,做m次半平面交,其实会m=2就是会正解了。
时间复杂度O(nmlogn)
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll,int>pii; const int N=1e5+7; struct node{ ll a,b,c; node(){a=b=0,c=1;} node(ll x,ll y){if(y<0)x=-x,y=-y;a=x/y;b=x%y;c=y;if(b<0)b+=c,a--;} ll ceil(){return a+(b>0);} bool operator<(const node&t)const{return a==t.a?b*t.c<t.b*c:a<t.a;} bool operator<=(const node&t)const{return a==t.a?b*t.c<=t.b*c:a<t.a;} }p[N]; int n,m,top,tot,id[N],ans[N],st[N]; ll a[N],b[N]; pii s[N<<1]; bool cmp(int x,int y){return a[x]<a[y]||a[x]==a[y]&&b[x]>b[y];} node cross(int x,int y){return node(b[y]-b[x],a[x]-a[y]);} void solve(int k) { top=tot=0; p[0]=node(0,1); for(int i=1;i<=n;i++) if(ans[id[i]]==-1&&a[id[i]]>a[st[top]]) { while(top&&cross(id[i],st[top]).a<p[top].ceil())top--; st[++top]=id[i]; if(top>1)p[top]=cross(st[top-1],id[i]); } p[top+1]=node(1ll<<60,1); for(int i=1;i<=n;i++) if(ans[i]>0) { int l=1,r=top-1,mid,pos=top; while(l<=r) { mid=l+r>>1; if(a[st[mid]]>=a[i]||cross(st[mid],i)<=p[mid+1])pos=mid,r=mid-1; else l=mid+1; } s[++tot]=pii(a[st[pos]]>=a[i]?0:cross(st[pos],i).a+1,1); l=2,r=top,pos=1; while(l<=r) { mid=l+r>>1; if(a[st[mid]]<=a[i]||p[mid]<=cross(st[mid],i))pos=mid,l=mid+1; else r=mid-1; } if(a[st[pos]]>a[i])s[++tot]=pii(cross(st[pos],i).ceil(),-1); } sort(s+1,s+tot+1); for(int i=1,j=1,sum=0;i<=top;i++) { while(j<=tot&&s[j].first<=p[i].ceil())sum+=s[j++].second; if(sum<k)ans[st[i]]=k; while(j<=tot&&s[j].first<=p[i+1].a) { int p=j; while(p<=tot&&s[p].first==s[j].first)sum+=s[p++].second; if(sum<k)ans[st[i]]=k; j=p; } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i],&b[i]),id[i]=i,ans[i]=-1; sort(id+1,id+n+1,cmp); for(int i=1;i<=m;i++)solve(i); for(int i=1;i<=n;i++)printf("%d ",ans[i]); }View Code