矩形分割(二分答案or二分搜索)

Description
平面上有一個大矩形,其左下角座標 (0,0),右上角座標 (R,R)。大矩形內部包含一些小矩形,小矩形都平行於座標軸且互不重疊。所有矩形的頂點都是整點。要求畫一根平行於y軸的直線x=k( k 是整數),使得這些小矩形落在直線左邊的面積必須大於等於落在右邊的面積,且兩邊面積之差最小。並且,要使得大矩形在直線左邊的的面積儘可能大。注意:若直線穿過一個小矩形,將會把它切成兩個部分,分屬左右兩側。
Input
第一行是整數 R,表示大矩形的右上角座標是 (R,R)。
接下來的一行是整數 N,表示一共有 N個小矩形。
再接下來有N 行。每行有4個整數 L T W H
表示有一個小矩形的左上角座標是 (L,T) ,寬度是W,高度是 H.
小矩形不會有位於大矩形之外的部分。
Output
輸出整數 n ,表示答案應該是直線 x=n 。 如果必要的話,x=R 也可以是答案。
Sample Input 1
1000
2
1 1 2 1
5 1 2 1
Sample Output 1
5
Hint
1≤ R≤ 10^6
0<N≤ 10000
0≤L,T≤ R,0≤W,H≤ R
Time Limit
1000MS
Memory Limit
256MB

分析題意:
要找的答案x本身就是單調的,我們不可能一個一個x的枚舉計算,這樣會超時,所以得把答案二分再搜索。
答案必須滿足要求:
1.這些小矩形落在直線左邊的面積必須大於等於落在右邊的面積
2.兩邊面積之差最小
3.大矩形在直線左邊的的面積儘可能大
可以發現題目其實想讓我們找一條分界線,這條分界線,使左右小矩形面積差最小(最接近0),且過了這條分界線,左右小矩形面積差就不是最小的了。如果我們換個角度看問題,把直線x=k左邊的小矩形面積總和看作一個數組,k爲下標,我們要在這個有限遞增的數組中找一個值:不小於所有小矩形總面積1/2,且最接近所有小矩形總面積1/2。同時,在元素可能重複的情況下,我們要找到符合要求的最後一個元素。這不就回歸到二分查找了嗎!二分查找可能重複元素的最後一個。只不過我們所查找的數組是通過我們自己計算,且不必計算所有元素。——這就是二分查找和二分答案的區別。
另外,由於題目的數據量比較大,此題應該開long long int

#include<stdio.h>
#include<algorithm>
using namespace std;

struct rectangle//記錄小矩形的各個數據
{//橫豎座標、寬、高
    long long int l,t,w,h;
}rtg[10000];//小矩形數組

struct cmp
{//矩形越左排位越前
    bool operator()(rectangle &r1,rectangle &r2)
    {
        return r1.l<r2.l;
    }
};
//數據量大,開long long
//大矩形座標R
//當前解x=ans左邊小矩形的面積left_area
//所有小矩形總面積sum_area
//答案ans
long long int R,left_area,sum_area=0,ans;
int N;//小矩形數目
//求x=k左側小矩形的總面積
long long int sum(long long int k)
{
    long long int sum=0;
    for(int i=0;i<N;i++)
    {
        if(rtg[i].l+rtg[i].w<=k)
           sum+=rtg[i].w*rtg[i].h;
        else if(rtg[i].l<k && rtg[i].l+rtg[i].w>k)
           sum+=(k-rtg[i].l)*rtg[i].h;
        else break;//已經按"越左越前"排過序,可以直接退出
    }
    return sum;
}

int main()
{
    scanf("%lld%d",&R,&N);
    for(int i=0;i<N;i++)
    {
        scanf("%lld%lld%lld%lld",&rtg[i].l,&rtg[i].t,&rtg[i].w,&rtg[i].h);
        sum_area+=rtg[i].w*rtg[i].h;//順便計算所有小矩形面積和
    }
    sort(rtg,rtg+N,cmp());//排序
    //x=mid左邊的小矩形總面積temp_left
    //左開右開區間(0,R+1),解絕對不可能是0、R+1
    long long int left=0,right=R+1,mid,temp_left;
    while(left+1!=right)//左開右開對應的結束邊界
    {   //因爲要找最後一個元素,故mid不妨向上取整
        mid=left+((right-left+1)>>1);
        temp_left=sum(mid);
        if(2*temp_left==sum_area){
            ans=mid;
            left_area=temp_left;
            //因爲要找最後一個,所以在(mid,right)繼續搜索
            left=mid;
        }
        else if(2*temp_left>sum_area){
            ans=mid;
            left_area=temp_left;
            //左邊大了,區間左移,排除mid,區間變爲(left,mid)
            right=mid;
        }
        //左邊太小,區間右移,排除mid,區間變爲(mid,right)
        else left=mid;
    }
    //保險起見,檢驗找到元素是否是重複元素中最後一個元素,若不是則調整
    while(ans<R && left_area==sum(ans+1)) ++ans;

    printf("%lld",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章