【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;
}

  

posted @ 2020-08-12 15:21  EM-LGH  阅读(65)  评论(0编辑  收藏