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