【UNR #4】序列妙妙值 分塊+DP

只會 80pts.  

最裸的暴力(40pts)   

令 $f[i][j]$ 表示當前 DP 到 $i$,劃分成了 $j$ 段的最小值.  

時間複雜度 $O(n^2)$   

一點優化(60 ~ 80pts) 

有幾個測點 $a[i]$ 很小,那麼可以直接開一個桶 $s[i][j]$ 表示前綴異或和爲 $i$,且劃分 $j$ 段的最小值.   

修改複雜度:$O(1)$,查詢複雜度 $O(v)$,總複雜度 $O(nv)$.   

還可以在 $trie$ 樹上亂搞,不知道能拿多少分.   

正解

分塊.   

我們發現 $60$ 分解法中查詢和修改複雜度差異很大,所以考慮用分塊去平衡上述複雜度.  

由於數字最大是 $2^{16}$,所以考慮維護 $mi[x][y]$ 表示一個數的前 $8$ 位是 $x$,去匹配一個後 $8$ 位爲 $y$ 的貢獻.  

那麼這個修改起來的話只需要枚舉後面的 $y$,複雜度爲 $O(\sqrt v)$.   

查詢的話後面是固定的,然後枚舉前面的 $x$,複雜度爲 $O(\sqrt v)$.  

總複雜度就是 $O(nK \sqrt v)$ 的.   

代碼: 

#include <cstdio>
#include <cstring>
#include <algorithm>  
#define N 60009  
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;  
const int inf=1000000000; 
int n,K; 
int a[N],f[N],tmp[N],mi[300][300];    
int main() { 
    // setIO("input");   
    scanf("%d%d",&n,&K);   
    for(int i=1;i<=n;++i) { 
        scanf("%d",&a[i]);      
        a[i]^=a[i-1];   
    }           
    f[0]=0; 
    for(int i=1;i<=n;++i) { 
        f[i]=inf; 
    } 
    int B=(1<<8)-1;     
    for(int i=1;i<=K;++i) { 
        for(int x=0;x<256;++x) {    
            for(int y=0;y<256;++y) mi[x][y]=inf; 
        }  
        for(int j=0;j<=n;++j) { 
            tmp[j]=inf;   
            for(int x=0;x<256;++x) {    
                //  前面固定,後面是猜的
                int det=mi[x][a[j]&B]+((a[j]>>8^x)<<8);  
                tmp[j]=min(tmp[j],det);     
            }     
            for(int x=0;x<256;++x) {    
                int cur=mi[a[j]>>8][x];    
                int det=f[j]+((a[j]&255)^x);  
                mi[a[j]>>8][x]=min(mi[a[j]>>8][x],det);  
            }
        }         
        for(int j=0;j<=n;++j) f[j]=tmp[j];  
    }              
    for(int i=K;i<n;++i) printf("%d ",f[i]);   
    printf("%d",f[n]); 
    printf("\n");  
    return 0;
}

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章