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;
}

 

posted @ 2018-08-27 23:03  CaptainLi  阅读(99)  评论(0编辑  收藏  举报