关于状态表示形式的优化方式
使用场景:需要记录不超过二进制数位(通常22位以内)的bool信息的DP问题。
常见的位操作
简单操作
-
任何二进制数位 &1 得到它本身。
-
任何二进制数位 ^1 则取反。
-
任何二进制数位 &0 则赋值为0。
-
任何二进制数位 |1 则赋值为1。
混合操作
-
(n>>k&1) 取出二进制下n的第k位(从右往左)。
-
n&((1<<k)-1) 取出二进制下n的右k位。
-
n^(1<<k) 将二进制下n的第k位取反。
-
n|(1<<k) 将二进制下n的第k位赋值为1。
-
n&(~(1<<k)) 将二进制下n的第k位赋值为0。
题目
海贼王的伟大航路
题面
从1节点开始不重复进过每一个点到达n节点,从i到j的时间是t[i][j],求最小时间。
状态
dp[i][j]表示到达点 j时经过的点的状态为二进制下的i的最少时间。
答案:cout<<dp[(1<<n)-1][n-1];
状态转移
for(int i=0;i<(1<<n);i++)//经过的状态
for(int j=0;j<=n-1;j++)//当前到达的点j
if((i>>j)&1)
for(int k=0;k<=n-1;k++)//枚举上一个点k
if((i>>k)&1)
dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+t[k][j]);
初始状态
dp[1][0]=0,其余为极大值。
P1879 [USACO06NOV] Corn Fields n
状态
dp[i][j]表示第i行种草状态为二进制下的j的方案数。
答案:
∑i=0(1<<n)−1dpm,i\sum\limits_{i=0}^{(1<<n)-1}dp_{m,i}i=0∑(1<<n)−1dpm,i。
状态转移
for(int i=1;i<=m;i++)//行
for(int j=0;j<(1<n);j++)//i行种草的状态
for(int k=0;k<(1<n);k+)//i-1行种草
if(check(i,j)==true)//肥沃、上左右不冲突
dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
初始状态
dp[0][0]=1。
check
check(i,j)保证第i行的草必须在肥沃的土地。
-
if((j&soil[i])==j)则种草合法;
-
if((j&k)==0)则上下不冲突;
-
if((j&(j<<1))==0),则左右不冲突。
P2704 [NOI2001] 炮兵阵地
状态
dp[i][j][k]表示前i行且第i行放置炮兵的状态为二进制下的j,第i-1行为二进制下的k时的最多炮兵数量。
答案:
∑i=0(1<<n)−1∑j=0(1<<n)−1dpn,i,j\sum\limits_{i=0}^{(1<<n)-1}\sum\limits_{j=0}^{(1<<n)-1}dp_{n,i,j}i=0∑(1<<n)−1j=0∑(1<<n)−1dpn,i,j。
状态转移
for(int i=1;i<=n;i++) //行
for(int j=0;j<(1<<m);j++) //第i行炮兵的状态
{
if(/*j二进制下的1有放在高地*/) continue;
if(/*j左右发现连续3个位置有冲突*/) continue;
for(int k=0;k<(1<<m);k++) //第i-1行状态
for(int p=0;p<(i<<m);p++) //第i-2行状态
{
if((k&j)||(p&j)) continue;
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+__builtin_popcount(j));
}
}
初始状态
dp[0][0][0]=0,其余memset。
优化
将有用的状态预处理,既可优化时间,也可优化空间。
P1896 [SCOI2005] 互不侵犯
状态
dp[i][j][p]代表前i行且第i行放置的棋子状态为二进制下的j,总棋子已经放入p个的方案数。
答案:
∑i=0(1<<n)−1dpn,i,K\sum\limits_{i=0}^{(1<<n)-1}dp_{n,i,K}i=0∑(1<<n)−1dpn,i,K。
状态转移
for(int i=1;i<=n;i++) //行
for(int j=0;j<(1<<n);j++) //棋子状态
for(int x=0;x<(1<<n);x++) //上一行棋子状态
for(int p=0;p<=K;p++) //总棋子数量
{
if((x&j)|(x<<1&j)|(x>>1&j)|(j<<1&j)) continue;
dp[i][j][p+__builtin_popcount(j)]+=dp[i-1][x][p];
}
初始状态
dp[0][0][0]=1;
也可使用上一题的优化。
P3092 [USACO13NOV] No Change G
状态
dp[j]表示硬币使用状态为二进制下的j时购买到的最大物品编号。
答案:
∑i=1n{ans=min(ans,count(i))dpi==nans=anselse\sum\limits_{i=1}^n \begin{cases} ans=\min(ans,count(i)) & dp_i==n \\ ans=ans & else \end{cases}i=1∑n{ans=min(ans,count(i))ans=ansdpi==nelse//count(i) 代表状态i对应的硬币面值之和
cout<<sum-ans。//sum代表总钱数
状态转移
for(int i=0;i<(1<<k);i++) //枚举硬币使用状态
for(int j=0;j<k;j++) //枚举当前使用的硬币
if(i>>j&1) //硬币j可以使用
dp[i]=max(dp[i],erfen(dp[i^(1<<j)],a[j])); //erfen代表二分

被折叠的 条评论
为什么被折叠?



