赞
踩
8/1多校赛1003血虐我 非得学会状压DP不可
感谢博主无私分享
传统做法是对每一行 i,暴力查找每一列 j 。 主要浪费时间在于检查是否有皇后会攻击,又花费o(n)。因此一共是o(n^3)。
改进算法引入状态压缩,依旧是每行i , 每列j 跑不掉的 o(n^2) ,不过判断上面可以用位运算花费o(1),实现复杂度降低
那么位运算是如何用于判断某个位置上是否收到攻击的呢? 举个例子吧
第1行放置的情况 1 0 0 0
第2 0 0 1 0
结合前两行,第3行在这些位置上在竖直方向上已经不能再放了( 约束条件 ) 1 0 1 0。按照这个思想,我们再算出主对角线上的约束条件,副对角线上的约束条件,通过总约束条件的约束,剩下的点就是可以放皇后的合法位置, 我们依次dfs这些合法位置就行。
所以现在问题就在于如何计算这些约束条件,这里有篇很好的文章(再次感谢博主无私分享,上面是我看完自己的理解,可以移步去这个链接了)下面记录转载过来的位运算笔记用自己的话理解
在状态压缩中,通常用 ( 1 << N ) - 1 来表示最大状态MAXST(全部都是1 ,11111111),用 A | B 来合并A和B的状态位,用 A & ~B 来清除A状态中所有的B中的状态位。
一、DFS函数参数Col,MainDiag,ContDiag的表示意义:
上面讲的,前 i行上的总列约束条件、主对角线约束、副对角线约束
二、Col == ( 1 << N ) - 1 的表示意义:
前i行把所有列上的总约束条件占满了 ,那么就是全摆上1了啊,也就是说找到了一种方案
三、emptycol = ( ( 1 << N ) - 1 ) & ~( Col | MainDiag | ContDiag ) 的表示意义:
111111 & (~ 00010) 则得到 1111101 , 相当于扣掉了总约束条件,剩下的合法序列
四、curcol = ( emptycol ) & ( ( ~emptycol ) + 1 ) 的表示意义:
也就是 lowbit = x & (-x) ,补码等于反码+1 , 比如说x等于 110 则 -x : 010 , 因此lowbit = 010,得到最右边的合法位置。 也就是用于遍历合法序列
五、 emptycol &= ~curcol 的表示意义:
类似于三,当前选中了curcol, 那么合法序列就扣掉当前位置
六、DFS递归的函数参数 Col | curcol,( MainDiag | curcol ) >> 1,( ContDiag | curcol ) << 1 的表示意义:
第一个参数在上面已经讲过了,现在讲第二个参数, 我们还是分析第1行对第2行的影响。
例如: 1 -> 0 1 0 0 0
第二行怎么找到自己主对角线上的 约束呢 ,其实只要把 第一行右移一位,然后照着第一个参数的方法累加,得到的就是对第二行的约束。 因为 (x,y) 的主对角线约束是 (x-1, y-1)。
第三个参数也就类似
以上都是本焫鸡通过自己能理解的方式理解的,要是有什么不对的地方请指出
- // 位运算
- #include<stdio.h>
- #include <iostream>
- using namespace std;
- int n,high,ans;
-
- // col 是前i-1行摆放的皇后 联合起来对 第i行的约束
- // fir 前i-1行 摆放的皇后,在主对角线方向上,对i行的约束
- // sec 是前i-1行摆放的皇后,在负对角线上对第i行的约束
-
- void dfs(int col,int fir,int sec)
- {
- if(col == high)
- {
- ans++;
- return;
- }
- int cur = high & (~(col|fir|sec)); //11111的情况 剔除掉 三座大山联合约束的情况 ,得到的序列, 存在某位为1 表示该位可以放
-
- while(cur)
- {
- int lowbit = cur &(-cur); //得到最后一个位置
- dfs(col|lowbit, (fir|lowbit)>>1 , (sec|lowbit)<<1);
- cur = cur & (~lowbit); // 最后一个1 标记为1 。 配合while循环。相当于把所以可能性遍历了一遍
- }
-
- }
- int main()
- {
- while(~scanf("%d",&n)&&n)
- {
- ans=0;
- high = (1<<n)-1;
- dfs(0,0,0);
- printf("%d\n",ans);
- }
- return 0;
- }

求到当前所用的列是第几位,然后带回去计算值,记忆话搜索剪枝
- #include <iostream>
- #include <bits/stdc++.h>
-
- using namespace std;
- const int maxn = 100;
-
- int mp[maxn][maxn], dp[maxn][maxn];
- int n,ans, high;
-
- int dfs(int col,int tmp, int i)
- {
- if(dp[col][i] != -1) return dp[col][i];
- if(col == high)
- {
- return dp[col][i] = tmp;
- }
- int last = high& (~col);
- int ans2 = 0;
- while(last)
- {
- int lowbit = last & (-last); // 得到最低位的1 的位置
- int cnt = log2(lowbit)+1;
-
- // cout<<cnt<<endl;
- ans2 = max(tmp,dfs(col|lowbit, tmp + mp[i][n-cnt+1], i+1));
- last = last & (~lowbit);
- }
- return dp[col][i] = ans2;
- }
- int main()
- {
- int t;
- scanf("%d",&t);
- while(t--)
- {
- memset(dp,-1,sizeof(dp));
- ans = 0;
- scanf("%d",&n);
- high = (1<<n)-1;
- for(int i=1; i<=n; i++)
- {
- for(int j=1; j<=n; j++)
- {
- scanf("%d",&mp[i][j]);
- }
- }
- printf("%d\n",dfs(0,0,1));
- }
-
- return 0;
- }

有N个怪兽,给出H[ ]数组,代表每个怪兽的HP值,给出 mp[ n ][ p ],表示第n个怪兽死了之后,获得它的装备可以对 第p 个怪兽造成的伤害( 每攻击一下 ), 另外,我们还有一把小手枪 每攻击一次 造成 1 伤害,问的是至少攻击几次能把所有怪兽杀死?
如果用状态压缩保存怪兽存亡状态,那么数组只需要开 1<<16,我们考虑的是杀小怪兽的顺序,和使用哪把武器( 这里无限使用 ),因此我们暴力杀怪兽的顺序,n ! 级别, 每杀一只怪兽都使用效果最好的武器( 贪心 ),因此搜索的结构就是(搜索第一步一定要确立好结构,之后再填上细节,剪枝)
- int dfs(int state)
- {
-
- for(怪兽:) //选出还活着的
- {
-
- for( 武器: ) // 贪心找出效果最好的
- {
- cos = min ;
- }
-
- DP[state] = max(DP[state], dfs(杀完小怪兽的state) + cos);
- }
-
- }
-
- main{
- dfs(0);
- }

我们可以用1 表示怪兽已经死了,这里使用的不是之前的位运算遍历, 因为状态的0 我们也需要考虑, 那么如果仅仅接受状态,而不接收n , 例如 state = 1 , 那么我们无法知道还活着的还有几只,也就是1前面有几个
- #include <iostream>
- #include <bits/stdc++.h>
-
- using namespace std;
- const int maxn = 1000;
- const int inf = 0x3f3f3f3f;
-
- int mp[maxn][maxn], dp[maxn], h[maxn];
- char st[maxn];
- int n,ans, high;
-
-
-
- int dfs(int state)
- {
- if(dp[state] != inf)
- return dp[state];
- if(state == high)
- return dp[state] = 0;
-
- for(int i=1; i<=n; i++) // 选择杀哪个怪物
- {
- if( !(state& (1<< (i-1) ) ) ) // 找出活的
- {
- // cout<<"I am alive"<<endl;
- int cnt = inf;
- for(int j=1; j<=n; j++) // 用哪个枪
- {
- if(state & (1<< (j-1) ) && mp[j][i] != 0) // 死了的为1 , 那么& 真, 就是能用这把枪
- {
- // cout<<"you are my gun"<<endl;
- int cos = h[i] / mp[j][i] + ((h[i] % mp[j][i])? 1:0) ;
- cnt = min(cnt, cos);
- }
- }
- if(cnt == inf)
- {
- cnt = h[i];
- }
- dp[state] = min(dp[state], dfs(state | ( (1<<i-1) )) + cnt);
-
- }
- }
- return dp[state] ;
- }
-
- int main()
- {
- int t;
- scanf("%d",&t);
- while(t--)
- {
- // memset(dp,-1,sizeof(dp));
- ans = 0;
- scanf("%d",&n);
- for(int i=1; i<=n; i++)
- scanf("%d",&h[i]);
- high = (1<<n) -1;
- for(int i=0; i<= high ; i++)
- dp[i] = inf;
-
- for(int i=1; i<=n; i++)
- {
- scanf("%s",st);
- for(int j=0; j<n; j++)
- {
- mp[i][j+1] = st[j]-'0';
- }
- }
- printf("%d\n",dfs(0));
- }
-
- return 0;
- }

题目大意:农夫有一块地,被划分为m行n列大小相等的格子,其中一些格子是可以放牧的(用1标记),农夫可以在这些格子里放牛,其他格子则不能放牛(用0标记),并且要求不可以使相邻格子都有牛。现在输入数据给出这块地的大小及可否放牧的情况,求该农夫有多少种放牧方案可以选择(注意:任何格子都不放也是一种选择,不要忘记考虑!
Sample Input
- 2 3
- 1 1 1
- 0 1 0
Sample Output
9
本题中,假设第i行允许 j 种放牛方式(即j种状态) ,那么对于每一种状态 k (k (= [1, j]),把上一行 i-1 上满足和 k 不冲突的(题目要求上下不能同时为1 )状态累加,相当于每个k对应一系列 i-1 行满足的状态 , 遍历 k 累加就行,直接上代码读起来容易些。
该类型题目由于状态很小,所以每次暴力查找状态。
- #include <cstdio>
- #include <cstring>
- using namespace std;
-
- #define mod 100000000
- const int maxn = 10050;
-
- int M,N,top = 0;
- //top表示每行最多的状态数
-
- int state[600],num[110];
- //state存放每行所有的可行状态(即没有相邻的状态
- //
-
- int dp[20][600];
- //dp[i][j]:对于前i行数据,每行有前j种可能状态时的解
- int cur[20];
- //cur[i]表示的是第i行整行的情况
-
- inline bool ok(int x) //判断状态x是否可行
- {
- if(x&x<<1)
- return false;//若存在相邻两个格子都为1,则该状态不可行
- return true;
- }
- void init() //遍历所有可能的状态
- {
- top = 0;
- int total = 1 << N; //遍历状态的上界
- for(int i = 0; i < total; ++i)
- {
- if(ok(i))
- state[++top] = i;
- }
- }
- inline bool fit(int x,int k) //判断状态x 与第k行的实际状态的逆是否有‘重合’
- {
- if(x&cur[k])
- return false; //若有重合,(即x不符合要求)
- return true; //若没有,则可行
- }
-
- int main()
- {
- while(scanf("%d%d",&M,&N)!= EOF)
- {
- init(); // 1 - 1<<N 的所以可行状态保存在state[1-top]中
- memset(dp,0,sizeof(dp));
- for(int i = 1; i <= M; ++i)
- {
- cur[i] = 0; // cur[i] 代表第i行草地的情况 1001 还是 0100等等
- int num;
- for(int j = 1; j <= N; ++j) //输入时就要按位来存储,cur[i]表示的是第i行整行的情况,每次改变该数字的二进制表示的一位
- {
- scanf("%d",&num); //表示第i行第j列的情况(0或1)
- if(num == 0) //若该格为0
- cur[i] +=(1<<(N-j)); //则将该位置为1(注意要以相反方式存储,即1表示不可放牧
- // 假设输入 1010 那么cur[i]为 1010 , 第一个读入的 左移 4-1 次
- }
- }
- for(int i = 1; i <= top; i++)
- {
- if(fit(state[i],1)) //判断所有可能状态与第一行的实际状态的逆是否有重合
- {
- dp[1][i] = 1; //若第1行的状态与第i种可行状态吻合,则dp[1][i]记为1
- }
- // 例如第一行本应该是 111 那么转换成了 000 , 则状态101 跟第一行fit为1 ,则dp[1][state - 101] = 1
-
- }
-
- /*
- 状态转移过程中,dp[i][k] =Sigma dp[i-1][j] (j为符合条件的所有状态)
- dp[i][k] = Sigma dp[i-1][j] (j为满足条件的所有状态)
- */
- for(int i = 2; i <= M; ++i) //i索引第2行到第M行
- {
- for(int k = 1; k <= top; ++k) //该循环针对所有可能的状态,找出一组与第i行相符的state[k]
- {
- if(!fit(state[k],i))
- continue; //判断是否符合第i行实际情况
- for(int j = 1; j <= top ; ++j) //找到state[k]后,再找一组与第i-1行符合,且与第i行(state[])不冲突的状态state[j]
- {
- if(!fit(state[j],i-1))
- continue; //判断是否符合第i-1行实际情况
- if(state[k]&state[j])
- continue; //判断是否与第i行冲突
- dp[i][k] = (dp[i][k] +dp[i-1][j])%mod; //若以上皆可通过,则将'j'累加到‘k'上
- // dp[i][k] = dp[i][k] + d[i-1][j] 第i行的状态 加上i-1行可用的状态。
- // 比如说 i行有两种状态 010 000 那么i就要考虑这两种情况, 当选择第一个010 时, i-1 有四种情况,也就是j有四种
- }
- }
- }
- int ans = 0;
- for(int i = 1; i <= top; ++i) //累加最后一行所有可能状态的值,即得最终结果!!!泥马写注释累死我了终于写完了!
- {
- ans = (ans + dp[M][i])%mod;
- }
- printf("%d\n",ans);
- }
- }

有一个N*M(N<=5,M<=1000)的棋盘,现在有1*2及2*1的小木块无数个,要盖满整个棋盘,有多少种方式?答案只需要mod1,000,000,007即可。
例如:对于一个2*2的棋盘,有两种方法,一种是使用2个1*2的,一种是使用2个2*1的。
由于行数n很小,那么我们每一列的状态最多只有 1<<5 种,做法就是针对每一列,搜索出所有合法状态。
当前列仅仅影响下一列,比如说第一列放了1*2 ,那么第二列该位置就不能再放。
状态转移方程 dp[ j+1 ][ nex ] += dp[ j ][ state ] , nex是由当前state限制下能构成的合法状态
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- #include<iostream>
- #include<string>
- #include<vector>
- #include<stack>
- #include<bitset>
- #include<cstdlib>
- #include<cmath>
- #include<set>
- #include<list>
- #include<deque>
- #include<map>
- #include<queue>
- using namespace std;
- typedef long long ll;
- const double PI = acos(-1.0);
- const double eps = 1e-6;
- const int INF = 0x3f3f3f3f;
- const int maxn = 100;
- const int mod = 1e9 + 7;
- int T, n, m;
- ll dp[11][(1 << 11) + 5];
-
- void dfs(int i,int j,int state, int nex)
- // 第几行,第几列,当前状态,对下一行的影响
- {
- if(i == n) // 到达第n+1 行的时候,当前列已经得到一种状态 state,且产生对下一行允许的 nex
- {
- dp[j+1][nex] += dp[j][state];
- return;
- }
- if( (1<<(i) &state) > 0) // 当前行 被占用了,那么就跳到下一行
- dfs(i+1,j,state,nex);
- else
- {
- dfs(i+1,j,state, nex|(1<<i));//在当前行摆上 1*2,则下一列被占用一个
-
- if(i+1<n && (((1<<(i+1)) & state) == 0) ) // 在当前列摆上2*1
- {
- dfs(i+2,j,state,nex);
- }
- }
- }
- int main()
- {
- while(~scanf("%d%d",&n,&m))
- {
- if(m == 0 && n == 0) break;
- memset(dp,0,sizeof(dp));
- dp[1][0] = 1;
- for(int j=1; j<=m; j++)
- {
- // 第j列
- for(int k=0; k<(1<<n); k++)
- // 各个状态
- {
- if(dp[j][k])//dp[j][k]!=0 表示第j行 k状态下,有方案可以放置木板,
- dfs(0,j,k,0);
- }
- }
-
- printf("%lld\n",dp[m+1][0]);
- }// 要求铺满,那么m+1 不能有露出来的}
- return 0;
- }

赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。