1. 程式人生 > >[BZOJ4069][Apio2015]巴厘島的雕塑

[BZOJ4069][Apio2015]巴厘島的雕塑

一個點 當前 天發 span 總結 滿足 區間 long oid

題目大意

分成 \(x\) 堆,是的每堆的和的異或值最小

分析

這是一道非常簡單的數位 \(DP\)

基於貪心思想,我們要盡量讓最高位的 \(1\) 最小, 因此我們考慮從高位向低位進行枚舉,看是否存在一種方案使得最高為不為 \(1\),如果不存在,那就填 \(1\)

因此我們要解決如下問題:

  1. 保存之前的狀態
  2. 考慮 \(A\), \(B\) 的限制

我們設已經考慮到 \(x\) 位,並且當前的值為 \(ans\)

顯然如果該位可以不填,它必須滿足存在一種狀態使得之前已經決定的狀態的異或和為 \(ans\), 並且新增的一段不會使得 \(ans\) 改變
那這就是一個經典的區間DP問題
我們不妨設 \(f[i][j]\)

表示前 \(i\) 個數分成 \(j\) 段,是否存在這一種情況
如果滿足上述條件,我們就能夠從 \(f[k][j - 1]\) 轉移到 \(f[i][j]\)

對於每個一位,如果有一種給情況使得當前為為0,那就讓 \(ans\) 的當前位為 \(0\)

復雜度:\(O(n^3logD)\)

對於最後一組數據:\(A =1, B \le n\),段數只有上限
我們要想把 \(f\) 數組的第二個維度省掉的話,用 \(f[i]\) 記錄將前 \(i\) 個數分段並得到可行解的最小段數,最後判斷其是否小於 \(B\),即可
復雜度:\(O(n^2logD)\)

總結

想了半天發現 \(O(n^3logD)\)

沒辦法優化,結果發現最後一個點的 \(A = 1\)

const int maxn = 2018;

typedef long long LL;
LL s[maxn];
int f[maxn][maxn], g[maxn];
LL ans = 0, t;
int n, A, B, len = 0;

bool valid(LL val, int dig) {
    return ((val >> (LL)dig | ans) == ans && (val & 1LL << (LL)dig-1LL) == 0);
}

void work1() {
    for(int x = len; x > 0; -- x) {
        forn(i, n) g[i] = INF;
        forn(i, n) Forn(j, i) if (g[j] < B) {
            t = s[i] - s[j];
            if(valid(t, x)) if (g[j] + 1 < g[i]) g[i] = g[j] + 1;
        }
        ans <<= 1LL;
        if(g[n] > B) ans ++;
    }
}

void work2() {
    for(int x = len; x > 0; -- x) {
        memset(f, 0, sizeof(f));
        f[0][0] = 1;
        forn(i, n) forn(j, i) for (int k = 0; k < i; ++ k) if(f[k][j-1]) {
            t = s[i] - s[k];
            //printf("x = %d i = %d j = %d k = %d t = %d\n", x, i, j, k, t);
            if(valid(t, x)) f[i][j] = 1;
        }
        int i;
        for(i = A; i <= B; ++ i) {
            if(f[n][i]) break;
        }
        ans <<= 1LL;
        if(i > B) ans ++;
    }
}

int main() {
    read(n, A, B);
    forn(i, n) {
        read(s[i]); s[i] += s[i-1];
    }
    len = (int)log2(s[n]) + 1;
    if (A == 1) work1();
    else work2();
    printf("%lld", ans);
    return 0;
}

[BZOJ4069][Apio2015]巴厘島的雕塑