题解 P1896 【[SCOI2005]互不侵犯】

Ykimna

2019-10-26 11:25:32

Solution

### 需要强调一点:状压dp也是 **dp**,需要普通动态规划基础,如没有普通动态规划基础还是先去巩固基础更为重要。 我也不知道我是否能讲清楚,如果哪里有疑问可以评论,或私信我(我在退役之前都能回答你) ------------ ### 前置知识:普通动态规划,位运算。 这道题题意非常简单就不再赘述,直接来讲讲数据处理方法。 如果一般的动态规划去想这道题,你会发现它的状态很难去表示和转移( ~~当然你也可以去开个10维数组~~ ),这就需要引入一种方法去表示它的状态,我们可以用一个数来表示它的状态,这个数每位只有两个数0或1,对于这道题来说1为国王,0为空格。 比如:10101 表示的就是: ![1](https://cdn.luogu.com.cn/upload/image_hosting/oudn4u12.png) 很显然如果棋盘有9格那么这个数会到1e8数组会炸,所以我们就需要用个方法来减维,那就是 **状压** 。因为这个数每位只0或1,那么很显然我们可以用二进制来表达这个状态,再把这个二进制转化为十进制来存储,1e8就就会退化为 $\log_21e8$ 最大也就为 $2^9-1(511)$ 。这样就可以用数组存下了。 接下来就来讲一下思路:我们先来处理每一行棋盘的所有合法状态。根据题意每个国王的左右不能有国王也就是只能插空站,在判断国王是否相邻(状态是否合法)有一种简单的方法 **&** 运算。这里举两个例子。 1. 10101 我先把这个数左移一位再与这个数进行 **&** 运算 ![2](https://cdn.luogu.com.cn/upload/image_hosting/fo964c1m.png) 2. 11010 ![3](https://cdn.luogu.com.cn/upload/image_hosting/fifzt8bs.png) 我们发现合法序列的在计算后的值为 $0$ ,而不合法的序列计算后的值不为 $0$ 。因此我们就可一从0枚举到 $2^n-1$ 把所有的合法序列存在 $sta[N]$ 数组里面。同时我们可以用 $king[N]$ 数组把每一个合法状态有多少个国王存下来。 代码如下: ```cpp int cnt=0; for(int i=0;i<=(1<<n)-1;i++) { if(i&(i<<1)) continue; sta[++cnt]=i;int gw=i; //存合法状态 while(gw>0)//计算国王数量 { if(gw%2==1) king[cnt]++; gw>>=1; } } ``` 之后的大致思路就是比较所有 $X$ 行和 $Y$ 行,其中 $X$ 为 $Y$ 的上一行,也就是说 $X=Y-1$ ,枚举所有 $X$ 行和 $Y$ 行的所有合法状态再计算出这 **两排** 的合法状态,因为想要整个棋盘合法,就要每一行棋盘都要与其上下两行棋盘合法 也就是国王的上方,左上,右上和下方,左下,右下都不能为国王。比较方法如下: ```cpp if(sta[j]&sta[k]) continue;//j枚举的是Y行的合法状态,k为X行的合法状态 if((sta[j]<<1)&sta[k]) continue; if(sta[j]&(sta[k]<<1)) continue; ``` 判断合法的状态都讲了,就可开始$dp$了,我们定义一个$dp[i][j][k]$数组,前i行,状态为j,使用了k个国王时的总方案数。再前文枚举$X$和$Y$合法后,我们就再枚举国王数就可以了。代码如下 ```cpp for(int c=0;c<=ki;c++) { if(king[j]+c>ki) break; dp[i][j][king[j]+c]+=dp[i-1][k][c]; } ``` ### 细节:处理好位运算的优先级,还有就是 ~~十年OI一场空,不开long long见祖宗~~ 下面贴完整代码: ```cpp #include<bits/stdc++.h> using namespace std; const int N=(1<<10)+20; const int M=17; int n,ki,cnt,king[N]; long long ans,sta[N],dp[M][N][M*M]; int main() { cin>>n>>ki; for(int i=0;i<=(1<<n)-1;i++) { if(i&(i<<1)) continue; sta[++cnt]=i;int gw=i; //存合法状态 while(gw>0)//计算国王数量 { if(gw%2==1) king[cnt]++; gw>>=1; } } for(int i=1;i<=cnt;i++)//初始化第一排,因为我们枚举的是X与X-1行 if(king[i]<=ki) //一行的国王数不可能超过国王的总数量 dp[1][i][king[i]]=1; for(int i=2;i<=n;i++)//枚举每行 { for(int j=1;j<=cnt;j++)//X行的合法状态 { for(int k=1;k<=cnt;k++)//X-1(Y)行的合法状态 { if(sta[j]&sta[k]) continue; if((sta[j]<<1)&sta[k]) continue; if(sta[j]&(sta[k]<<1)) continue; for(int c=0;c<=ki;c++)//国王数 { if(king[j]+c>ki) break; dp[i][j][king[j]+c]+=dp[i-1][k][c]; } } } } for(int i=1;i<=cnt;i++)//因为记录的是前i行,所以加上在前n行的所有合法状态 ans+=dp[n][i][ki]; cout<<ans<<endl; return 0; } ``` ~~码字不易,反正不要钱多少赞一个~~