1. 程式人生 > >題解——[APIO2015]巴厘島的雕塑 貪心+DP+特殊數據優化

題解——[APIO2015]巴厘島的雕塑 貪心+DP+特殊數據優化

ret div 沒有 最大 set ongl 每次 亂搞 api

寫了好久。。。。

剛剛調了一個小時各種對拍,,,,最後發現是多寫了一個等號,,,,內心拒絕

表示一開始看真的是各種懵逼啊

在偷聽到某位大佬說的從高位開始貪心後發現可做

首先考慮小數據(因為可以亂搞)

所以先從高位開始枚舉位數:

for(int k=all; k ;k--)

ans表示當前答案,f[i][j]表示dp到第i為,分為j段,是否可以滿足第k位為0

ans初始為0,每次DP完就修改ans,

所以枚舉i和j,然後枚舉上一段的結尾l,

由於是or,所以之前已經有的是消不掉的,

於是有 if(!f[l][j-1]) continue;

這樣就可以保證之前的都已經滿足了,所以只要考慮當前段就好了

sum[i]表示到i的前綴和。

先獲取當前段大小now=sum[i] - sum[l]

所以如果now的第k位為0,並且之前的高位都不與當前ans沖突,就可以轉移了

因為如果高位與ans沖突,而前面已經最優了,所以如果沖突只能是不那麽優,

於是後面再優都補不回來了。其實這也是從高位貪心的意義所在



最後如果在f[n][A ~ B]中有任意一個為true的話,ans就不用修改(因為默認已經是0了)

而如果一個都沒有,那麽就將ans的當前位變成1,

那麽考慮如何判斷,

首先運算符肯定只能用or,不然是會遺漏信息的(也許可以用其他的吧,但是我沒想到)

考慮這樣兩個二進制串:

now :101100 1011

ans : 101100 0000

中間的空格代表把第k位和後面隔開,

通過一些二進制的基本性質(其實也是所有進制的基本性質)可以發現,

如果前面都是相同的話,因為ans的小於k的位數都還不確定,都是0,

所以最大相差也就是2^(k-1),

反之如果前面有沖突,那麽由於沖突在前面,相差一定會超過2^(k-1),

所以直接判斷now和ans的差就可以了?

不是!


因為now是中間過程,所以now是可能小於ans的,

所以如果是這樣的情況的話:

now : 100110 1011

ans : 101100 0000

這樣的話now < ans,所以now - ans 一定小於 2^(k-1),-1是因為第k位對應的是2^(k-1),幾次方是從0開始數的

但是這樣是不合法的,之所以這樣會被誤判為合法,是因為它之前的那個粗體0幫後面的粗體1抵消掉了。

因此為了這樣的0不會對真正不合法的1造成幹擾,

我們只需要計算(now | ans) - ans < 2^(k-1)是否成立即可,

因為這樣的話,所有這樣的0都會變為1,於是這時要再有不合法的1出現就無法被抵消了。

於是小數據就解決了,但是考慮到最大的點n <= 2000,這樣的復雜度肯定是過不去的

但是觀察發現,對於最大的那幾個點,滿足A == 1,

而之前的f數組之所以要開二維,還要枚舉段數,就是因為只有枚舉了所有情況才能保證不漏掉A~B之間的答案,

因為段數小不一定更優,不然可能會找到小於A的答案,

但是因為A == 1,就不可能會找到小於A的答案了,這時也滿足段數小的一定更優,

所以我們直接令g[i]表示dp到i,使第k位為0的最小段數,

然後轉移時判斷條件和上面一樣即可,註意初始化g[0]=0,其余為極大值

下面放代碼:

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 2100
#define getchar() *o++
#define LL long long
char READ[5000100],*o=READ;
int n,A,B,all;
int s[AC],g[AC];
LL sum[AC];//g[i]是適用於沒有下限的情況
LL ans;
bool f[AC][250];

inline int read()
{
    int x=0;char c=getchar();
    while(c > 9 || c < 0) c=getchar();
    while(c >= 0 && c <= 9) x=x*10+c-0,c=getchar();
    return x;
}

void work1()//f[i][j]代表前i位分成j段,第k位能否在保證前k位都不變且不考慮後面all-k位的情況下是否可能與當前ans相同(此時第k位默認為0)
{//這樣的話相當於給了一個判斷依據ans,於是就可以提高復雜度以解決問題
    LL now;//獲取最高位數
    bool done=false;
    all=log2(sum[n]) + 1;
    for(R k=all; k ;k--)//枚舉位數(從高位開始貪心)
    {
        memset(f,0,sizeof(f));
        f[0][0]=true;
        done=false;//error!!!每次都要重置
        for(R i=1;i<=n;i++)//枚舉到了哪裏
        {
            int be=min(i,B);
            for(R j=1;j<=be;j++)//枚舉分的段數
            {
                for(R l=j-1;l<i;l++)//枚舉上一段的結尾,error!!!上一段啊怎麽可以等於。。。。
                {
                    if(!f[l][j-1]) continue;//因為是|,所以前面有1不能消掉,所以前面不滿足就不能取了
                    now=sum[i] - sum[l];//獲取區間和
                    if(((now | ans) - ans) < (1LL<<(k-1)))//error!!!註意longlong 
                    {//如果相差小於當前位,代表不一樣的都在後面,就沒關系了
                        f[i][j]=true;
                        break;
                    }
                }
                if(i == n)
                {
                    if(j >= A && j <= B && f[i][j])
                    {
                        done=true;
                        break;//如果找到了就無需再找了
                    }
                }
            }
        }
        if(!done) ans+=1LL<<(k-1);//因為1已經有一位了,所以只要移動k-1位就可以了(其實是因為本來就只要判斷2^(k-1),因為第一位對應的是2^0)
    }
    printf("%lld\n",ans);
}
//g[i]代表滿足這位為0最少需要的段數
void work2()
{
    LL now;
    all=log2(sum[n]) + 1;
    for(R k=all; k ;k--)
    {
        memset(g,127,sizeof(g));
        g[0]=0;
        for(R i=1;i<=n;i++)//枚舉樹
        {
            for(R j=0;j<i;j++)//枚舉上一段的結尾,,,懶得特判1了,就從0開始吧
            {
                now=sum[i] - sum[j];
                if(((now | ans) - ans) < (1LL<<(k-1))) g[i]=min(g[i],g[j]+1);//error!!!註意longlong 
            }//因為要求最小的,所以就不能跳過了
        }
     /*   printf("!!!%d:\n",k);
        for(int i=1;i<=n;i++) printf("%d ",g[i]);
        printf("\n");*/
        if(g[n] > B) ans+=1LL<<(k-1);//error!!!貌似在這裏也要加LL吧 
    }
    printf("%lld\n",ans);
}

void pre()
{
    n=read(),A=read(),B=read();
    for(R i=1;i<=n;i++) s[i]=read(),sum[i]=sum[i-1] + (LL)s[i];
}

int main()
{
    freopen("in.in","r",stdin);
//    freopen("sculpture.in","r",stdin);
//    freopen("sculpture.out","w",stdout);
    fread(READ,1,5000000,stdin);    
    pre();
    if(A == 1) work2();//特殊點特殊處理
    else work1();
    fclose(stdin);
//    fclose(stdout);
    return 0;
}

題解——[APIO2015]巴厘島的雕塑 貪心+DP+特殊數據優化