1. 程式人生 > >【AtCoder】【模擬】【模型轉化】Camel and Oases(AGC012)

【AtCoder】【模擬】【模型轉化】Camel and Oases(AGC012)

題意:

有一個駱駝,n個綠洲遍佈在數軸上,第i個綠洲的座標為x[i],保證x[i]單增。駱駝的駝峰有體積初始值V。當駝峰的體積變為v的時候,駝峰中至多隻能夠儲存v L的水。駱駝希望走完所有的綠洲,並且可以向下面這樣來走:
1.走距離d,消耗駝峰中d L的水,但是駝峰的體積不會減少。任意時候駝峰中的水的體積均不能夠為負數;
2.跳躍到任意一個位置,消耗完所有的水,並且讓駝峰的體積變為v/2。該操作在v=0的時候是不能夠進行的。
駱駝能夠在綠洲將水補滿至v。且一個綠洲可以多次訪問並進行補給。最後要求你輸出從每個位置出發,能否走完所有的綠洲。

資料範圍:

N,V<=2*1e5 ,-1e9<=x[i]<=1e9 ,且x是單增的。

思路:

首先看完題目,我們可以發現v/2這個操作是十分玄學的。這意味著只會減少log(V)+1次,就不能夠再進行任何的移動了。也說明了v的取值只會有log(V)+2種,這個數量級是很小的。
那麼對於某一個v而言,駱駝能夠在一些連續的綠洲之間任意的穿梭,也就形成了一些線段。具體而言就是假如說x[i+1]-x[i]<=v,那麼這兩個綠洲就是聯通的,就可以讓i和i+1處於一條線段中。我們可以發現,當v從V變換到0的時候,每一條線段的長度是在逐漸變短的,而線段的數量在逐漸增多。也就是說,v越小,駱駝的移動能力越差。現在這個問題就變成了強迫你選擇了第一層(也就是v=V的那一層)中的某一條線段,在剩下的每一層當中,選出至多一條線段,能否存在一種方式使得最後選出來的所有的線段能夠覆蓋完所有的綠洲。
對於這個問題而言,瞬間就簡化了不少。可以定義f1[state],表示在選擇state(第i位為0表示第i層沒有選擇線段,第i位為1表示第i層選擇了一條線段)的時候,(線段從最左邊開始選)能夠覆蓋完全的最靠右的位置;定義f2[state],表示在選擇state的時候,(線段從最右邊開始選)能夠覆蓋完全的最靠左的位置。最後再掃一遍第一層中的所有的線段,表示強行選擇其中的某一條線段,然後找是否有一個state,滿足第0位不為1(第一層已經被確定了),且f1[state],f2[全集-state-1],加上這條線段,使得能夠覆蓋完所有的綠洲。
這樣子看上去似乎問題已經解決了,但是當V奇小的時候,第一層最壞可能有n條線段(一個點一條),然後就變成了\(O(n2^{log_n})=O(n^2)\)

的狀態。仔細分析之後,我們可以發現,假如第一層的線段數量大於了logV+2,那麼由於接下來的線段只會越來越短並且越來越多,就算是選擇第一層的所有線段也需要>logV+2條線段,而一共才能夠選擇log(V)+2條線段線段,那就顯然不可能有解了。全部輸出Impossible即可。
這樣子就變為了O(logV * n)的時間複雜度了。

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000000
#define INF 2000000000
using namespace std;
int n,V,x[MAXN+5];
int cnt[25];//cnt用於計算每一層所有的線段數
int l[25][MAXN+5],r[25][MAXN+5];//l表示線段的左端點,r表示的則是右端點
int f1[MAXN*5+5],f2[MAXN*5+5];//如思路中的定義
bool ans[MAXN+5];//ans記錄的是對於某一條線段的答案,不是某個綠洲的答案
void Init()
{
    for(int i=0;i<=MAXN*5+3;i++)
        f1[i]=0,f2[i]=INF;
}
int UpFind(int id,int pos)//找l在pos+1的左邊的線段,也就是能夠向右擴充套件的最靠右的線段
{
    pos++;
    int p=upper_bound(l[id]+1,l[id]+cnt[id]+1,pos)-l[id];
    p--;
    if(p<=0)
        return pos;
    return max(r[id][p],pos-1);
}
int LowFind(int id,int pos)//找r在pos-1的右邊的線段,也就是能夠向左擴充套件的最靠左的線段
{
    pos--;
    int p=lower_bound(r[id]+1,r[id]+cnt[id]+1,pos)-r[id];
    if(p>=cnt[id]+1)
        return pos;
    return min(l[id][p],pos+1);
}
int main()
{
    Init();
    scanf("%d %d",&n,&V);
    int logV=0;
    for(logV=0;(1<<logV)<=V;logV++);//求出來的實際上是logV+1
    for(int i=1;i<=n;i++)
        scanf("%d",&x[i]);
    x[n+1]=INF;
    x[0]=-INF;//便於操作
    for(int LG=0;LG<=logV;LG++)
    {
        int d=V/(1<<LG);
        cnt[LG]=1;
        l[LG][1]=1;
        //求線段
        for(int i=1;i<=n;i++)
        {
            r[LG][cnt[LG]]=i;
            if(x[i+1]-x[i]>d)
            {
                cnt[LG]++;
                l[LG][cnt[LG]]=i+1;
            }
        }
        cnt[LG]--;
    }
    if(cnt[0]>logV+1)//特判
    {
        for(int i=1;i<=n;i++)
            printf("Impossible\n");
        return 0;
    }
    int all=(1<<(logV+1));
    f1[0]=0,f2[0]=n+1;//預處理兩個f陣列
    for(int s=0;s<all;s+=2)
        for(int i=0;i<=logV;i++)
        {
            if(!(s&(1<<i)))
                continue;
            f1[s]=max(f1[s],UpFind(i,f1[s-(1<<i)]));
            f2[s]=min(f2[s],LowFind(i,f2[s-(1<<i)]));
        }
    for(int i=1;i<=cnt[0];i++)//列舉第一層的每一條線段
    {
        int ln=l[0][i],rn=r[0][i];
        for(int s1=0;s1<all;s1+=2)
        {
            int s2=all-1-s1-1;
            int lpos=f1[s1];
            int rpos=f2[s2];
            if(lpos>=ln-1&&rpos<=rn+1)//看能否覆蓋所有的綠洲
            {
                ans[i]=true;//存的是線段的答案
                break;
            }
        }
    }
    int pos=1;
    for(int i=1;i<=n;i++)
    {
        if(ans[pos]==true)
            printf("Possible\n");
        else
            printf("Impossible\n");
        if(x[i+1]-x[i]>V)
            pos++;
    }
    return 0;
}