BZOJ4347 : [POI2016]Nim z utrudnieniem

将石子从小到大排序,然后DP。

设$f[i][j][k]$表示考虑了前$i$堆的石子,当前扔掉的堆数模$d$为$j$,没有扔掉的石子的异或和为$k$的方案数。

因为石子排过序,所以转移的复杂度为$O(md)$。

对于空间的问题,注意到$f[i][j][k]$和$f[i][j][k\ xor\ a[i]]$的转移是互补的,于是可以同时处理,省去滚动数组,直接做到原地DP,当然$f[i][0][k]$要特别处理。

最后注意特判$n$是$d$的倍数的情况,此时答案应该减去$1$。

 

#include<cstdio>
const int N=1048576,P=1000000007;
int n,d,m,p,i,j,k,x,a[N],f[10][N],g[N];
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline int add(int a,int b){
  a+=b;
  if(a>=P)a-=P;
  return a;
}
int main(){
  for(read(n),read(d);i<n;i++){
    read(j),a[j]++;
    if(j>m)m=j;
  }
  for(f[0][0]=i=p=1;i<=m;i++){
    while(p<=i)p<<=1;
    while(a[i]--){
      for(k=0;k<p;k++)g[k]=add(f[d-1][k],f[0][k^i]);
      for(j=d-1;j;j--)for(k=0;k<p;k++)if(k<=(k^i)){
        x=f[j][k];
        f[j][k]=add(f[j-1][k],f[j][k^i]);
        f[j][k^i]=add(f[j-1][k^i],x);
      }
      for(k=0;k<p;k++)f[0][k]=g[k];
    }
  }
  return printf("%d",add(f[0][0],P-(n%d==0))),0;
}

  

posted @ 2015-11-30 13:35 Claris 阅读(...) 评论(...) 编辑 收藏