藍橋杯 試題 歷屆試題 區間移位 二分

問題描述
  數軸上有n個閉區間D1,…,Dn。其中區間Di用一對整數[ai, bi]來描述,知足ai < bi。已知這些區間的長度之和至少有10000。因此,經過適當的移動這些區間,你總可使得他們的「並」覆蓋[0, 10000]——也就是說[0, 10000]這個區間內的每個點都落於至少一個區間內。
  你但願找一個移動方法,使得位移差最大的那個區間的位移量最小。
  具體來講,假設你將Di移動到[ai+ci, bi+ci]這個位置。你但願使得maxi |ci|  最小。
輸入格式
  輸入的第一行包含一個整數n,表示區間的數量。
  接下來有n行,每行2個整數ai,  bi,以一個空格分開,表示區間[ai, bi]。保證區間的長度之和至少是10000。
輸出格式
  輸出一個數,表示答案。若是答案是整數,只輸出整數部分。若是答案不是整數,輸出時四捨五入保留一位小數。
樣例輸入
2
10 5010
4980 9980
樣例輸出
20
樣例說明
  第一個區間往左移動10;第二個區間往右移動20。
樣例輸入
4
0 4000
3000 5000
5001 8000
7000 10000
樣例輸出
0.5
樣例說明
  第2個區間往右移0.5;第3個區間往左移0.5便可。
數據規模和約定
  對於30%的評測用例,1 ≤ n ≤ 10;
  對於100%的評測用例,1 ≤ n ≤ 10000,0 ≤ ai < bi  ≤ 10000。
解題思路:
  • 二分:相似最大化最小值( 此題要求的求 最小的max|ci| )或者最小化最大值問題,均可以用二分很好的解決。咱們定義:
  C( mid ):任意區間移動的距離都小於等於mid
用二分找到知足條件的d便可。

  • 每次C( mid )的判斷用貪心解決。

這一題可能貪心的策略有不少,好比以左端從小到大排序,按順序選取知足條件的區間。或者以右端從小到大排序。node

以左端從小到大排序能夠找到反例說明可能形成有短區間浪費沒用到的狀況。函數

而以右端點爲何就不會呢?(這裏給一個個人解釋,但只是直觀解釋,沒有嚴格的數學證實)spa

  • 對於相鄰的兩個區間,他們的關係有①②兩種,對於關係①,兩種排序方式都結果相同;而對於關係②,若是以左端點排序,

可能會形成短區間的浪費,而以右端點排序就會首先考慮段區間,因此綜合考慮以右端點排序的方式更優。3d

  • 咱們假設cur爲當前已經覆蓋的連續區間的右端點,每次以右端點從小到大排序選擇,則cur相較於其餘方案是最小的,

這樣右邊剩餘的空間最大,能夠有更多的區間提供選擇。code

(關於用右端點排序最優的數學證實我尚未想出來,若是哪位大佬知道但願能夠指導我一下)blog


 

對於區間是否選擇以及cur的更新:排序

    假設當前區間能夠移動的最大距離爲mid, 那麼某個區間能夠移動的範圍爲 [ l - mid, r + mid ],知足這個條件的區間可使用,與cur有以下四種關係:ci

  綜合以後 ①②爲一種狀況,③④爲一種狀況。數學

 


  • 最後可能有小數的狀況,由於剛開始區間位置都是整數,設若是一個區間要移動x( x爲小數 ),那麼就有一個區間要移動 y (y爲小數),且x+y=z, z爲整數,

題目要求最小,那麼 x==y==z/2,因此處理時只須要將每一個區間都乘以2,最後判斷結果是否能被2整除。it


  • 實現代碼:
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

//輸入
int n; 

struct node//區間 
{
    int l;
    int r;
    node(int l1,int r1){ l = l1 ; r = r1;}//構造函數 
};

vector<node> v;

bool cmp(node n1, node n2)
{
    if( n1.r==n2.r )    return n1.l<n2.l;//若是右端點相等,選擇更接近cur的 
    return n1.r<n2.r;//以右端點從小到大排序 
}

bool C(int mid)//判斷mid是否知足條件 
{
    int cur = 0;
    vector<node> v_(v);
    
    while( true )
    {
        bool flat = false;//有沒有知足條件的區間 
        for(int i=0; i<v_.size(); i++)
        {
            int l = v_[i].l, r = v_[i].r;
            if( l-mid<=cur&&cur<=r+mid )//知足條件 
            {
                if( cur-l>=mid )//狀況 1,2 
                {
                    //cur += mid + r - cur;
                    cur = mid+r;
                }
                else//狀況 3,4 
                {
                    cur += (r-l);// cur += len
                }
                v_.erase(v_.begin()+i);//去掉此區間 
                flat = true;
                break;//每次找到一個區間cur的狀態都改變,原來不能移動的區間如今可能能夠移動 
            }
        }
        if( !flat || cur>=20000 )    break;//若是沒有知足條件的區間||已經覆蓋了全部區間 
    }
    return cur>=20000;
}

void solve()
{
    sort(v.begin(),v.end(),cmp);
    
    int l = 0, r = 20000;
    while( r-l>1 )//二分法求解 
    {
        int mid = (l+r)/2;
        if( C(mid) )    r = mid;//知足條件,縮小移動距離 
        else    l = mid;
    }
    
    if( r%2==0 )    printf("%d\n",r/2);
    else    printf("%.1f\n",(float)r/2.0);
}

int main()
{
    scanf("%d",&n);
    while( n-- )
    {
        int l,r;
        scanf("%d%d",&l,&r);
        v.push_back(node(2*l,2*r));//每一個區間乘以2 
    }
    
    solve();
    
    return 0;
}
相關文章
相關標籤/搜索