题解 P2915 【[USACO08NOV]奶牛混合起来Mixed Up Cows】

wjyyy

2018-07-02 17:34:23

Solution

[欢迎在博客食用](http://www.wjyyy.top/691.html) ## 解法:    首先我们看到数据范围N≤16,就可以想到状态压缩,接着看就会看到方案数的选择,那么就可以尝试DP。    首先我想的是,用三维数组$f[i][j][k]$表示当前已经选了i头牛,上一个选的是j,当前状态压缩为k。我们主要关注的是第二维,因为方案合不合法就看与自己前面的**那一个**的编号差距。因此有了方案:枚举当前要放的牛m,再枚举之前放过的牛c,这样枚举1到65535,找出有c且没有m的状态。为了避免**冲突**,我们还要在最外面枚举当前选了多少头牛。    因此这个时间复杂度是$O(N^3\times 2^n)$的,乘出来有2亿~~我为什么要乘出来~~。    所以这种做法是不可行的,不过bzoj时限比较宽,可以过,luogu开O2再加玄学剪枝就可以![](http://www.wjyyy.top/wp-content/uploads/2018/07/201807021718.png)了,emmm。。。代码贴在最后。 ## 正解:    学过集合/了解二进制的同学应该知道,我们如果用二进制枚举子集,在合法条件下,后枚举的一定包含某些先枚举的,例如**111011**会在**110011**后枚举到。因为数值越来越大,1也会趋向越多。那么我们就可以使大体趋势跟随1~65535枚举来更新我们的DP状态。首先我们定义DP数组$f[i][j]$,表示当前最后一头牛是i时状态压缩为j的方案数。那么我们在二进制枚举下,枚举数对**(j,k)**,找到合法数对,使得在当前二进制状态下,j被放进去过,且为最后一头,同时k没有被放进去过,找到后更新状态。因为1存的是已经放过的奶牛,所以在奶牛增加的情况下,通过刷表法更新之后状态下的奶牛,就不会出现紊乱/冲突了。 ## Code:100pts ```cpp #include<cstdio> #include<cstring> int abs(int x) { return x>0?x:-x; } long long f[18][66666];//当前放到i号牛时压位为j int num[18]; int main() { memset(f,0,sizeof(f)); int n,K; scanf("%d%d",&n,&K); for(int i=1;i<=n;i++) scanf("%d",&num[i]); for(int i=1;i<=n;i++) f[i][1<<(i-1)]=1; //正在枚举的状态一定包含已经枚举过的状态 for(int i=1;i<(1<<n);i++) for(int j=1;j<=n;j++)//j是来自 for(int k=1;k<=n;k++)//k是当前 if(abs(num[j]-num[k])>K&&((1<<(j-1))&i)&&(((1<<(k-1))&i)==0)) f[k][((1<<(k-1))|i)]+=f[j][i];//刷表 long long sum=0; for(int i=1;i<=n;i++)//统计答案 sum+=f[i][(1<<n)-1]; printf("%lld\n",sum); return 0; } ``` ## Code:80~100pts(O2) 4维枚举 ```cpp #include<cstdio> #include<cstring> int abs(int x) { return x>0?x:-x; } long long f[2][18][66666];//当前放到i号牛时压位为j int num[18]; int lowbit(int x) { return x&(-x); } int cnt(int x)//二进制数里有多少个1 { int sum=0; while(x) { sum++; x-=lowbit(x); } return sum; } int main() { memset(f,0,sizeof(f)); int n,k,tmp; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&num[i]); for(int i=1;i<=n;i++) f[1][i][1<<(i-1)]=1; for(int i=2;i<=n;i++)//放第i头牛 //可行状态 for(int j=1;j<=n;j++) for(int t=1;t<=n;t++) { if(abs(num[j]-num[t])>k) { if(t==1)//这是什么玄学剪枝 tmp=2; else tmp=1; for(int k=1;k<1<<n;k+=tmp) if(cnt(k)==i-1) { if(!((1<<(j-1))&k)&&((1<<(t-1))&k))//j没有被用过且t被用过 f[i&1][j][k|(1<<(j-1))]+=f[i&1^1][t][k]; } } } long long sum=0; for(int i=1;i<=n;i++)//统计方案 sum+=f[n&1][i][(1<<n)-1]; printf("%lld\n",sum); return 0; } ```