SCOI2007 组队
这道题该怎么做呢……我自己只能想出来O(n^3)的暴力模拟……不过并不行。
来看一下正解吧……将给定的式子变一下形,得到A*height + B*speed - C <= A*minh+B*mins.
这样的话,把所有的运动员按照A*height + B*speed - C从小到大排序,这样就使得,如果有i成立,那么对于每个j<i,只要这个j的height和speed都不小于最小要求都是合法的。
但是不只有这一个限制。由于height还必须大于等于minh,所以对于任意一个运动员,其speed不仅要大于等于mins还要小于等于mins + C/B(这个用上面的式子可以推出)所以我们进行枚举,先枚举mins,之后再枚举minh,对于当前的minh,我们按照sum的顺序从小到大去枚举,如果这个运动员满足其speed >=mins && <= mins + C/B,那么这个运动员当前就是可以取的。这样我们维护了一个右区间,之后,我们再维护左区间,对于每一个height不够的运动员,我们把他从合法区间中踢出。不过有一些本身并没有被算过,所以只有对于s符合要求的那些,我们才会把其删除。这样每次在找完合法区间之后更新答案即可。
这种算法起到优化的作用在于其利用了单调性。我们来看,在从小到大枚举minh,mins的过程中,我们知道A*minh+B*mins.必然是单调递增的,也就是说,每次向后取一个元素,就会导致更多的人有被选中的机会。而对于已经被删除的人,因为我们枚举的最小高度肯定也是递增的,所以已经被删除的人将来也不可能合法。于是这个算法就用单调性来保证了它的正确性,同时完成了时间复杂度的优化。我们只需要O(2*n^2)的复杂度就可以过了。
看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define rep(i,a,n) for(ll i = a;i <= n;i++) #define per(i,n,a) for(ll i = n;i >= a;i--) #define enter putchar('\n') using namespace std; const int M = 5005; typedef long long ll; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct node { int h,s,sum; }Sum[M],H[M],S[M]; bool cmp1(node a,node b) { return a.sum < b.sum; } bool cmp2(node a,node b) { return a.h < b.h; } bool cmp3(node a,node b) { return a.s < b.s; } int n,A,B,C,l,r,cnt,mins,lims,limsum,ans; int main() { n = read(),A = read(),B = read(),C = read(); rep(i,1,n) { Sum[i].h = read(),Sum[i].s = read(),Sum[i].sum = Sum[i].h * A + Sum[i].s * B - C; H[i].h = Sum[i].h,H[i].s = Sum[i].s,H[i].sum = Sum[i].sum; S[i].h = Sum[i].h,S[i].s = Sum[i].s,S[i].sum = Sum[i].sum; } sort(Sum+1,Sum+1+n,cmp1); sort(H+1,H+1+n,cmp2); sort(S+1,S+1+n,cmp3);//上面是按照关键字排序 rep(i,1,n) { l = 0,r = 0,cnt = 0; mins = S[i].s,lims = S[i].s + C / B;//确定限制范围 rep(j,1,n) { limsum = A * H[j].h + B * mins; while(r < n && Sum[r+1].sum < limsum) { r++; if(mins <= Sum[r].s && Sum[r].s <= lims) cnt++;//将合法元素选中 }//判断合法区间的末尾 while(l < n && H[l+1].h < H[j].h) { l++; if(mins <= H[l].s && H[l].s <= lims) cnt--; }//判断合法区间开头并且删除不合法元素 ans = max(ans,cnt);//更新答案 } } printf("%d\n",ans); return 0; }
当你意识到,每个上一秒都成为永恒。