【C++心路历程33】【USACO 2016 February Contest, Bronze Problem 3.】 Load Balancing(加难)

本文介绍了如何使用C++解决USACO竞赛中关于农场分割的平衡问题。通过建造南北和东西向的栅栏将农场分为四块,目标是找到最佳的栅栏位置以使每块区域的牛数量尽可能均衡。讨论了不同复杂度的算法,包括n^3的基础算法和利用树状数组优化到O(n * log^2 1000000)的解决方案。

【问题描述】

  FJ的n头牛都站在二维农场上的一些坐标点(x1,y2)…(xn,yn)上,其中xi和yi都是正奇数,大小不会超过1,000,000。FJ想以建造一条很长(实际上是无限长)的南北向的栅栏的方式来分割他的农场,这个栅栏表示的直线符合直线方程(或者说一次函数)x=a(a是一个偶数,请确保FJ不会将栅栏穿过任何一头牛)。FJ同时还想建造另一条很长(实际上也是无限长)的东西向的栅栏,这个栅栏表示的直线同样满足直线方程y=b,其中b是一个偶数。这两条栅栏相交于点(a,b),并且这两条栅栏一起将他的农场分成四块。
  FJ想选择a和b使得出现在这四块区域里的牛的个数是“平衡的”,即没有一块区域里有太多的牛。如果使得m是四块区域里牛的数量的最大值,那么FJ想让m最小。请帮助他决定m的最小值。

【输入格式】

  第一行的输入是一个整数n,下面n行每行包含一只牛的位置,即其的x和y坐标。

【输出格式】

  只有一行,即最小的m。

【输入样例】

7
7 3
5 5
7 13
3 1
11 7
5 3
9 1

【输出样例】

2
【分析】
原题数据实在太水 n^3算法乱搞搞就行了。

【数据范围】
30%的数据:1≤n≤300
50%的数据:1≤n≤1000
100%的数据:1≤n≤100000

30分算法:

void solve30()
{
    int aa=maxn;
    for(int i=1;i<=n;i++)
    {
        int xx=x[i]+1;
        for(int j=1;j<=n;j++)
        {
            int yy=y[j]+1;
            int ans1=0,ans2=0,ans3=0,ans4=0,ans;
            for(int kk=1;kk<=n;kk++)
            {
                if(E[kk].x<xx && E[kk].y<yy) ans1++;
                else if(E[kk].x<xx && E[kk].y>yy) ans2++;
                else if(E[kk].x>xx && E[kk].y<yy) ans3++;
                else if(E[kk].x>xx && E[kk].y>yy) ans4++;
            }
            ans=max(ans1,ans2);ans=max(ans3,ans);ans=max(ans4,ans);
            aa=min(aa,ans);
        }   
    }
    printf("%d",aa);
}

50分优化:对于每次枚举的x,y坐标则可以使用二分猜:当上方的牛多些,则下次在下半部分猜,反之亦然,每次猜时记录最优值。O( n*n*log2 1000000) (也可以猜下标)
P.S:在猜的同时记录答案是因为猜的时候没有考虑奇偶性,难以判断,在途中猜可以避免麻烦。

bool check(int xx,int mid)
{
    int yy=mid;
    int ans1=0,ans2=0,ans3=0,ans4=0,ans;
    for(int kk=1;kk<=n;kk++)
    {
        if(E[kk].x<xx && E[kk].y<=yy) ans1++;
        else if(E[kk].x<xx && E[kk].y>yy) ans2++;
        else if(E[kk].x>xx && E[kk].y<=yy) ans3++;
        else if(E[kk].x>xx && E[kk].y>yy) ans4++;
    }
    ans=max(ans1,ans2);ans=max(ans3,ans);ans=max(ans4,ans);
    aa=min(ans,aa);
    if(ans==ans2||ans==ans4) return false;
    return true;
}
void solve50()
{
    int aa=maxn;
    for(int i=1;i<=n;i++)
    {
        int xx=x[i]+1;
        int A=1,B=1000000,ww;
        while(A<B)
        {
            int mid=(A+B)/2;
            if(check(xx,mid)) ww=mid,B=mid-1;
            else ww=mid,A=mid+1;
        }
    }
    printf("%d",aa);
}

100分算法:
对于x基准线的移动,我们发现每次其实只有1个点从右边移至左边,而要查询的正好是小于y基准线的元素个数,这正好符合树状数组维护动态集合的思想。
实现:开lc,rc两个树状数组,最开始元素全部在rc,x移动便将一个元素从rc删除,移至lc。
O( n*log2 1000000*log2 1000000)

int lowbit(int x){return -x&x;}
void update(int x,int *c,int flag)
{
    if(flag==1)
    while(x<=1000000)
        c[x]++,x+=lowbit(x);
    else
    while(x<=1000000)
        c[x]--,x+=lowbit(x);
}
int sum(int x,int *c)
{
    int ret=0;
    while(x>0)
        ret+=c[x],x-=lowbit(x);
    return ret;
}
bool check(int mid,int i)
{
    int yy=mid;
    int ans1=0,ans2=0,ans3=0,ans4=0,ans;
    ans1=sum(yy,lc);
    ans2=i-ans1;
    ans3=sum(yy,rc);
    ans4=n-i-ans3;
    ans=max(ans1,ans2);ans=max(ans3,ans);ans=max(ans4,ans);
    aa=min(aa,ans);
    if(ans==ans2||ans==ans4) return false;
    return true;
}
void solve100()
{
    sort(E+1,E+1+n);
    for(int i=1;i<=n;i++)
        update(E[i].y,rc,1);
    for(int i=1;i<=n;i++)
    {
        update(E[i].y,rc,-1);update(E[i].y,lc,1);
        int A=0,B=1000000,ww;
        while(A<B)
        {
            int mid=(A+B)/2;
            if(check(mid,i)) ww=mid,B=mid-1;
            else ww=mid,A=mid+1;
        }
    }
    printf("%d",aa);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值