【BZOJ 4347】[POI2016]Nim z utrudnieniem

来源:互联网 发布:c语言 char 编辑:程序博客网 时间:2024/06/11 20:47

今天第一次写blog那就从现在做的第一题开始写吧。。

问题可以转化成取走若干堆使得剩下堆异或和为0。朴素的做法是用f[i][j][k]大概表示前i堆,已经取的堆数%d==j,取走的异或和为k的方案数,暴力转移时间复杂度O(ndmaxai)

首先优化时间,由于ai107,可以考虑将ai从小到大排序,对于每个 iai 二进制的每一位都置1作为k的上界,这个上界显然2ai,这样时间复杂度就降低到O(dai)

接下来优化空间,滚动数组无法完全解决空间问题,j,k这两维难以缩小,考虑将i缩小到一维。如果要取走第i堆的话,k只能转移到k^aik^ai也只能转移到k,如果不取第i堆的话,不需要转移。可以通过尝试调整一下DP的顺序和状态的设计。

#include<bits/stdc++.h>using namespace std;typedef long long ll;const int N=500005;const int MOD=1000000007;int n,d,A,p,a[N],f[11][1<<20];inline void add(int&x,int y){x=x+y<MOD?x+y:x+y-MOD;}inline void read(int&x){char c;while((c=getchar())<'0'||c>'9');x=c-'0';while((c=getchar())>='0'&&c<='9')x=x*10+c-'0';}int main(){    int i,j,k;    scanf("%d%d",&n,&d);    for(i=1;i<=n;++i)read(a[i]),A^=a[i];    sort(a+1,a+1+n);    f[0][0]=1;    for(p=i=1;i<=n;++i){        for(;p<=a[i];p<<=1);        for(k=p-1;~k;--k)f[d][k]=0;        for(j=d-1;~j;--j)for(k=p-1;~k;--k)add(f[j+1][k^a[i]],f[j][k]);        for(k=p-1;~k;--k)add(f[0][k],f[d][k]);     }    int ans=f[0][A];    if(n%d==0)ans=(ans-1+MOD)%MOD;     printf("%d",ans);    return 0;}