状压dp - 棋盘问题(学习)

本文详细介绍状态压缩动态规划方法,通过多个实例讲解如何利用位运算优化递归过程,解决棋盘放置问题、骨牌覆盖问题等典型算法题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/math_coder/article/details/9671581

如果看不明白代码的位运算,请先了解下位运算的应用

---------------------------------------------------------------------------------------------------------------------------

**********此文章属于原创,看此文章前请先参考论文   周伟《动态规划之状态压缩》**********

问题1:

        在n*n(n≤20)的方格棋盘上放置n个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。

如果用组合学的角度来考虑此问题,那么非常简单:

        我们一行一行放置,第一行有n种选择,第二行n-1,……,最后一行有1种选择,根据乘法原理,答案就是n!

这里我们介绍另一种解法:状态压缩递推(States Compressing Recursion,SCR)。

  • 我们仍然一行一行放置。
  • 取棋子的放置情况作为状态,某一列如果已经放置棋子则为1,否则为0。这样,一个状态就可以用一个最多20位的二进制数表示。
  • 例如n=5,第1、3、4列已经放置,则这个状态可以表示为01101(从右到左)。设fs为达到状态s的方案数,则可以尝试建立f的递推关系。
  • 考虑n=5,s=01101
  • 因为我们是一行一行放置的,所以当达到s时已经放到了第三行。又因为一行能且仅能放置一个车,所以我们知道状态s一定来自: 

            ①前两行在第3、4列放置了棋子(不考虑顺序,下同),第三行在第1列放置; 

            ②前两行在第1、4列放置了棋子,第三行在第3列放置; 

            ③前两行在第1、3列放置了棋子,第三行在第4列放置。

        这三种情况互不相交,且只可能有这三种情况,根据加法原理,fs应该等于这三种情况的和。写成递推式就             是:

         f(01101) = f(01100) + f(01001) + f(00101);

  • 根据上面的讨论思路推广之,得到引例的解决办法: 

        f(0) = 1;

        f(s) = ·f(s-2^i);

          其中s的右起第i+1位为1(其实就是在枚举s的二进制表示中的1)

代码如下:

[cpp]  view plain  copy
 print ?
  1. #include<iostream>  
  2. #include<cstdio>  
  3. using namespace std;  
  4. long long f[1<<20];  
  5.   
  6. int main(){  
  7.     long long n;  
  8.     while(cin>>n){  
  9.         memset(f,0,sizeof(f));  
  10.         f[0] = 1;  
  11.         long long i,t;  
  12.         for( i=1; i< 1<<n; i++){  
  13.             for( t=i; t>0; t -= (t & -t)){  
  14.                 f[i] += f[i & ~(t & -t)];  //注意理解  
  15.             }  
  16.         }  
  17.         cout<<f[(1<<n)-1]<<endl;   
  18.     }  
  19. }  


 

问题2:在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互
      相攻击的方案总数。
输入:给你一个n和m,分别表示棋盘的大小,不能放的格子总数
      接下来是m行坐标,表示不能放置的位子。
输出:符合条件的方案总数。

0表示可以放置,1表示不能放置
0 1 0
0 0 1
0 0 0
输入:
3 2 
1 2 
2 3 
输出:3
输入:
4 1
1 1 
输出:3*3*2*1 == 18
提示:
1 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0

[cpp]  view plain  copy
 print ?
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<algorithm>  
  4. #include<cstring>  
  5. using namespace std;  
  6.   
  7. long long f[1<<20];  
  8. long long vist[22];  
  9.   
  10. int main(){  
  11.     long long n,m;  
  12.     while(cin>>n>>m){  
  13.         memset(f,0,sizeof(f));  
  14.         memset(vist,0,sizeof(vist));  
  15.         for(int i=0;i<m;i++){  
  16.             int a,b;  
  17.             cin>>a>>b;  
  18.             vist[a] += 1<<(b-1); //某一个位置不能放置,把这个位置压缩成整数   
  19.         }  
  20.         f[0] = 1;  
  21.         for(int i=1;i< 1<<n; i++){  
  22.             int num = 0;  
  23.             for(int j=i;j>0;j -= (j & -j)) num++; //计算 i 这一个状态 1 的个数,也就是最多包涵的行数   
  24.             for(int j=i;j>0;j -= (j & -j)) {  
  25.                 if(!(vist[num]&(j & -j))) f[i] += f[ i& ~(j & -j)];//判断该位置是否可以放置   
  26.             }  
  27.         }  
  28.         cout<<f[(1<<n)-1]<<endl;  
  29.     }  
  30. }  


问题3:给出一个n*m 的棋盘(n、m≤80,n*m≤80),要在棋盘上放k(k≤20)个棋子,
使得任意两个棋子不相邻。求可以放置的总的方案数

[cpp]  view plain  copy
 print ?
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cmath>  
  4. #include<algorithm>   
  5. using namespace std;  
  6. int s[1<<10],c[1<<10],f[82][1<<9][21];   
  7. int n,m,num,flag,val;  
  8.   
  9. void DFS(int ans,int pos,int flag){  ///////////////////////  
  10.     if(pos>n) {   
  11.         s[++num] = ans;   
  12.         c[num] = flag;  
  13.         return;   
  14.     }  
  15.     DFS(ans,pos+1,flag);  
  16.     DFS(ans+(1<<pos-1),pos+2,flag+1);  
  17. }  
  18.   
  19. int main(){  
  20.     while(cin>>n>>m>>val){  
  21.         if(n>m) swap(n,m); // 行列交换   
  22.         num = 0;  // 状态数初始化    
  23.         DFS(0,1,0); // 参数:当前状态,位置,1的个数 ,找出一行中符合条件的总数   
  24.         memset(f,0,sizeof(f));  
  25.         ///////////////////////////////////////////  
  26.         for(int i=1;i<=num;i++) //第一行进行初始化,状态中有多少个1就初始多少   
  27.             f[1][s[i]][c[i]] = 1;  
  28.         for(int i=2;i<=m;i++){ //第几行   
  29.             for(int j=1;j<=num;j++){ //某一行的某个状态   
  30.                 for(int r=1;r<=num;r++){ //上一行的某个状态   
  31.                     if(!(s[j]&s[r])){ //当前行和上一行状态不冲突   
  32.                         for(int k=0;k<=val;k++){ //枚举当前一行棋子的个数   
  33.                             if(k>=c[j]) f[i][s[j]][k] += f[i-1][s[r]][k-c[j]]; //借助上一行的状态枚举当前状态   
  34.                         }  
  35.                     }  
  36.                 }  
  37.             }  
  38.         }  
  39.         long long sum=0;  
  40.         for(int i=1;i<=num;i++) // 累加最后一行符合条件的总数   
  41.             sum += f[m][s[i]][val];  
  42.         cout<<sum<<endl;  
  43.     }  
  44. }  
  45. /* 
  46.  
  47. 由于数据缺乏,自己模拟了几组数据  
  48. 输入:3 3 2  
  49. 输出:23 
  50.  
  51. 输入: 2 2 2 
  52. 输出: 2 
  53.  
  54. 输入:20 1 2 
  55. 输出: 171 
  56.  
  57. */   


 

问题4: 
在n*n(n≤10)的棋盘上放k 个国王(可攻击相邻的8 个格子),求使它们无法
互相攻击的方案数。

 

[cpp]  view plain  copy
 print ?
  1. #include<iostream>   
  2. #include<cmath>  
  3. #include<algorithm>  
  4. #include<cstring>  
  5. #include<cstdio>  
  6. using namespace std;  
  7. long long f[11][1<<10][30];  
  8. int s[1<<10],c[1<<10],num;  
  9. int n,m,val;  
  10.       
  11. void DFS(int ans,int pos,int flag){  
  12.     if(pos>n){  
  13.         s[++num] = ans;  
  14.         c[num] = flag;  
  15.         return ;  
  16.     }  
  17.     DFS(ans,pos+1,flag);  
  18.     DFS(ans+(1<<pos-1),pos+2,flag+1);  
  19. }  
  20.   
  21. int main(){  
  22.     while(cin>>n>>m>>val){  
  23.         num = 0;  
  24.         DFS(0,1,0);  
  25.         memset(f,0,sizeof(f));   
  26.         for(int i=1;i<=num;i++)  
  27.             f[1][s[i]][c[i]]=1;  
  28.         for(int i=2;i<=n;i++){  
  29.             for(int j=1;j<=num;j++){ //当前行   
  30.                 for(int r=1;r<=num;r++){ //上一行   
  31.                     if((s[j]&s[r]) || ((s[j]>>1)&s[r]) || ((s[j]<<1)&s[r])) continue//八个方向判断   
  32.                     for(int k=0;k<=val;k++){  
  33.                         if(k>=c[j]) f[i][s[j]][k] += f[i-1][s[r]][k-c[j]];  
  34.                     }  
  35.                 }  
  36.             }  
  37.         }  
  38.         long long sum=0;  
  39.         for(int i=1;i<=num;i++)  
  40.             sum += f[n][s[i]][val];  
  41.         cout<<sum<<endl;  
  42.     }  
  43. }  
  44. /* 
  45.  
  46. 输入:3 3 2 
  47. 输出:16 
  48.  
  49. 输入:2 2 1 
  50. 输出:4 
  51.  
  52. 输入:3 3 3 
  53. 输出:8 
  54.  
  55. 输入:9 9 2 
  56. 输出:1968  
  57.  
  58. */   

题目5:给出一个n*m(n≤100,m≤10)的棋盘,一些格子不能放置棋子。求最多能在

棋盘上放置多少个棋子,使得每一行每一列的任两个棋子间至少有两个空格。 

[cpp]  view plain  copy
 print ?
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<algorithm>  
  4. #include<cstring>  
  5. #include<cmath>  
  6. using namespace std;  
  7. long long f[11][1<<10];  
  8. int n,m,s[1<<10],c[1<<10];  
  9. int num,ant,a[1<<10];  
  10.   
  11. void DFS(int ans,int pos,int flag){  
  12.     if(pos>m){  
  13.         s[++num] = ans;  
  14.         c[num] = flag;  
  15.         return ;  
  16.     }  
  17.     DFS(ans,pos+1,flag);  
  18.     DFS(ans+(1<<pos-1),pos+3,flag+1);  
  19. }  
  20.   
  21. int main(){  
  22.     while(cin>>n>>m>>ant){  
  23.         int p,q;  
  24.         for(int i=0;i<ant;i++){  
  25.             cin>>p>>q;  
  26.             a[p] += (1<<q-1);  
  27.         }  
  28.         memset(f,0,sizeof(f));  
  29.         num = 0;  
  30.         DFS(0,1,0);  
  31.       //  for(int i=1;i<=num;i++) cout<<s[i]<<" "<<c[i]<<endl;  
  32.         for(int i=1;i<3 && i<=n;i++){  
  33.             for(int j=1;j<=num;j++){ // i==2时,表示当前行 , i==1时表示当前行   
  34.                 if(i==1){  
  35.                     if(a[i]&s[j]) continue;  // 不能放置  
  36.                     f[i][s[j]]=c[j];  
  37.                 }  
  38.                 else {  
  39.                     if(a[i]&s[j]) continue;  // 不能放置  
  40.                     for(int r=1;r<=num;r++){ // 不能放置并且和状态 1 不冲突  , 表示前一行的状态   
  41.                         if(a[i-1]&s[r]) continue;  // 不能放置   
  42.                         if((s[j]&s[r])) continue;  
  43.                         f[i][s[j]] = max(f[i][s[j]],f[i-1][s[r]]+c[j]);   
  44.                     }  
  45.                 }  
  46.             }  
  47.         }  
  48.         for(int i=3;i<=n;i++){  
  49.             for(int j=1;j<=num;j++){//当前行   
  50.                 if(s[j]&a[i]) continue;  
  51.                 for(int r=1;r<=num;r++){//上一行 i-1  
  52.                     if((s[r]&a[i-1]) || (s[j]&s[r])) continue;  
  53.                     for(int h=1;h<=num;h++){//再上一行 i-2  
  54.                         if((s[h]&a[i-3])||(s[j]&s[h])||(s[r]&s[h])) continue;  
  55.                         f[i][s[j]] = max(f[i][s[j]],f[i-2][s[h]]+c[j]+c[r]);  
  56.                     }  
  57.                 }  
  58.             }  
  59.         }  
  60.         long long sum = 0;  
  61.         for(int i=1;i<=num;i++)   
  62.             sum = max(f[n][s[i]],sum);  
  63.         cout<<sum<<endl;  
  64.     }  
  65. }   
  66. /* 
  67.  
  68. 输入:  
  69. 3 3 3 
  70. 2 1 
  71. 2 2 
  72. 2 3 
  73. 输出:2  
  74.  
  75. */  


题目6:  
给出n*m(1≤n、m≤11)的方格棋盘,用1*2 的长方形骨牌不重叠地覆盖这个
棋盘,求覆盖满的方案数。


[cpp]  view plain  copy
 print ?
  1. #include<iostream>  
  2. #include<cstdio>   
  3. #include<algorithm>  
  4. #include<cstring>  
  5. #include<cmath>  
  6. #include<string>  
  7. using namespace std;  
  8. long long f[12][1<<14];  
  9. int s1[1<<14],s2[1<<14],s[1<<14],ss[1<<14];  
  10. int n,m,num,flag;  
  11. bool vist[1<<14];  
  12.   
  13. void DFS(int ans1,int ans2,int pos){  
  14.     if(pos>n) {  
  15.         if(pos==n+1) {  
  16.             s1[++num] = ans1;  
  17.             s2[num] = ans2;  
  18.         }  
  19.         return ;  
  20.     }  
  21.     DFS(ans1,ans2,pos+1); // 不放   
  22.     DFS(ans1+(1<<pos-1)+(1<<pos),ans2,pos+2); // 横放   
  23.     DFS(ans1+(1<<pos-1),ans2+(1<<pos-1),pos+1); // 竖放   
  24. }  
  25.   
  26. void dfs(int ans,int pos){  
  27.     if(pos>n){  
  28.         if(pos==n+1)  
  29.             s[++flag] = ans;  
  30.         return;  
  31.     }  
  32.     dfs(ans,pos+1);  
  33.     dfs(ans+(1<<pos-1)+(1<<pos),pos+2);  
  34. }  
  35.   
  36. int main(){  
  37.     while(cin>>n>>m){  
  38.         if(n==0 && m==0) break;  
  39.         if(n%2 && m%2){ cout<<'0'<<endl; continue; }  
  40.         if(n > m) swap(n,m);  
  41.         if(n%2) swap(n,m);  
  42.         if(m==1) { cout<<'1'<<endl; continue; }  
  43.         num = 0; flag = 0;  
  44.         DFS(0,0,1);  
  45.         dfs(0,1); // 第一行所有状态搜索   
  46.         memset(f,0,sizeof(f));   
  47.         for(int i=1;i<=flag;i++)  
  48.             f[1][s[i]] = 1;  
  49.         int ant = (1<<n) - 1;  
  50.         for(int i=1;i<=num;i++){ //第二行   
  51.             ss[i] = s1[i];  
  52.             for(int j=1;j<=flag;j++){ //第一行   
  53.                 if((s2[i]&s[j])) continue;  
  54.                 if((ant-s2[i])&(ant-s[j])) continue//处理相同的 0    
  55.                 f[2][s1[i]] += f[1][s[j]];  
  56.             }  
  57.         }  
  58.         sort(ss+1,ss+num);  
  59.         int ans = 0;  
  60.         ss[++ans] = ss[1];  
  61.         for(int i=2;i<=num;i++){  //除去相同的 ,避免重复计算   
  62.             if(ss[i]!=ss[i-1]) ss[++ans] = ss[i];  
  63.         }  
  64.         for(int i=3;i<=m;i++){ //第三行  
  65.             for(int j=1;j<=num;j++){//第 i 行状态遍历   
  66.                 for(int r=1;r<=ans;r++){ // 第 i-1 行 *****相同的 s1[r]只能计算一次 WA了N久   
  67.                     if(s2[j]&ss[r]) continue;  
  68.                     if((ant-s2[j])&(ant-ss[r])) continue;  
  69.                     f[i][s1[j]] += f[i-1][ss[r]];  
  70.                 }   
  71.             }  
  72.         }   
  73.         cout<<f[m][(1<<n)-1]<<endl;  
  74.     }  
  75. }  
<think>我们首先需要理解用户的需求:用户希望了解缩动态规划(DP)的详细原理和应用。根据引用资料,我们可以总结出DP的核心思想、适用条件以及基本实现方法。 ### 缩动态规划(DP)详解 缩动态规划是一种通过将态信息缩(通常使用二进制)来表示动态规划态的方法,适用于态中每个元素只有两种可能(如存在/不存在、选中/未选中)且态规模较小(通常不超过32个元素)的问题。 #### 1. 适用条件 - **态单元简单**:每个态单元只有两种态(例如0/1)。 - **态规模小**态单元的数量不能太大(通常不超过32,因为一个32位整数可以表示32个态单元)。 - **需要**:如果不缩,态数量会非常大(例如$2^n$级别),导致无法存储。 #### 2. 基本原理 DP的核心在于用一个整数的二进制位来表示一个集合。例如,一个长度为$n$的二进制数,每一位(0或1)表示某个元素的态(如是否被选中)。这样,一个整数就代表了一个集合。 例如,有5个元素,用5位二进制数表示: - 二进制数`10101`(十进制21)表示第0、2、4个元素被选中(从右向左,最低位为第0位)。 - 这样,我们就可以用一个整数(如21)来代表一个集合。 #### 3. 位运算基础 DP中常用的位运算操作: - **左移(<<)**:`1 << i` 表示将1左移i位,得到第i位为1,其余为0的数。常用于构造某个态。 - **与(&)**:判断某一位是否为1,例如`state & (1<<i)`,如果结果非0,则第i位为1。 - **或(|)**:将某一位设为1,例如`state | (1<<i)`。 - **异或(^)**:将某一位取反,例如`state ^ (1<<i)`。 - **取反(~)**:将每一位取反,例如`~state`(注意整数位数,避免高位干扰)。 #### 4. DP的步骤 DP的解题步骤与普通DP类似,但态表示使用了缩的整数: 1. **定义**:通常用$dp[i][state]$表示处理到第$i$个阶段(或位置)且当前态为$state$(一个缩的整数)时的最优解(或方案数)。 2. **态转移**:根据问题的规则,从之前的态转移到当前态。通常需要枚举所有可能的前一个态,检查是否满足转移条件。 3. **初始化**:确定初始态(如$dp[0][0]=0$)。 4. **结果提取**:根据最终态(如$dp[n][final\_state]$)得到答案。 #### 5. 经典问题举例:旅行商问题(TSP) 问题描述:有$n$个城市,旅行商从起点0出发,访每个城市恰好一次后回到起点,求最短路径。 - **态表示**:$dp[state][i]$表示已经访过的城市集合为$state$,当前位于城市$i$的最短路径长度。 - **态转移**:$dp[state][i] = \min\{ dp[state \setminus \{i\}][j] + dist[j][i] \}$,其中$j$是上一个访的城市,且$j$在$state$中,$state \setminus \{i\}$表示从集合$state$中移除城市$i$(即态中第$i$位置0)。 - ****:$state$是一个$n$位的二进制数,第$k$位为1表示城市$k$已经访过。 #### 6. 代码示例(TSP问题) ```python def tsp(dist): n = len(dist) # 态总数:2^n total_states = 1 << n # 初始化dp数组:dp[state][i]表示在态state下,当前在i城市的最短路径 dp = [[float('inf')] * n for _ in range(total_states)] # 起点:态1<<0表示只包含0号城市,当前在0城市,路径长度为0 dp[1][0] = 0 # 遍历所有态 for state in range(total_states): # 遍历当前态下的每个城市 for i in range(n): # 如果当前态中不包含i,跳过 if not (state & (1 << i)): continue # 尝试从其他城市j转移到i for j in range(n): # 如果态中包含j,且j不是i,则尝试转移 if i != j and (state & (1 << j)): # 从态state中移除i,得到前一个态prev_state prev_state = state ^ (1 << i) # 更新dp[state][i] dp[state][i] = min(dp[state][i], dp[prev_state][j] + dist[j][i]) # 最终态:所有城市都访过(全1),并且回到起点0 res = float('inf') for i in range(1, n): # 最后一步从i回到0 res = min(res, dp[total_states-1][i] + dist[i][0]) return res ``` #### 7. 优化与技巧 - **滚动数组**:当态转移只依赖前一阶段时,可优化空间。 - **预处理合法**:对于某些问题,可以先预处理出所有合法态(如不冲突的态),减少枚举量。 - **剪枝**:在态转移过程中,通过条件判断提前终止无效转移。 #### 8. 应用场景 DP常用于组合优化问题,如: - 旅行商问题(TSP) - 铺砖问题(用1x2砖块铺满网格) - 任务分配问题(每个任务分配给不同的人,每人只能一个- 棋盘覆盖问题(如炮兵布阵) ### 总结 缩动态规划通过将缩为整数,有效解决了态空间过大问题。其核心在于位运算的应用和态转移的设计。掌握DP需要熟悉位运算操作,并能够将问题态抽象为二进制表示。 §§相关问题§§ 1. DP中常用的位运算操作有哪些? 2. 如何判断一个态是否包含某个元素? 3. 旅行商问题(TSP)的态转移方程是什么? 4. DP适用于哪些类型的问题? 5. 在缩中,如何表示一个集合的并集、交集和差集?</think>### 缩动态规划(DP)详解 #### 一、核心概念 DP是一种将**集合缩为整数**的动态规划技术。它适用于态中的每个元素只有两种可能(如存在/不存在、选中/未选中),且态规模较小(通常不超过32个元素)的问题。核心思想是将一个集合映射为一个整数的二进制位: - **二进制表示**:用整数的二进制位表示集合元素态 例如:集合 $\{A,C\}$ 在3元素系统中可表示为二进制 `101`(十进制5) - ****:将多维缩为单整数 态 $dp[i][S]$ → $dp[S]$,其中 $S$ 是缩后的整数态 #### 二、适用条件(引用[3]) 1. **态单元二元化**:每个态单元仅需两种态(0/1) 2. **态规模小**态单元数 $n \leq 32$(int存储上限) 3. **态可**态数据能映射为二进制整数 4. **态依赖复杂**:多个元素态共同影响决策(引用[2]) #### 三、位运算基础(关键操作) | 操作 | 符号 | 示例(二进制) | 作用 | |---------------|------|----------------------------|--------------------------| | 判断元素存在 | `&` | `state & (1<<i)` | 检查第 $i$ 位是否为1 | | 添加元素 | `\|` | `state \| (1<<i)` | 将第 $i$ 位置1 | | 删除元素 | `&~` | `state & ~(1<<i)` | 将第 $i$ 位置0 | | 切换元素态 | `^` | `state ^ (1<<i)` | 翻转第 $i$ 位 | | 枚举子集 | `&` | `sub = (sub-1) & state` | 遍历态的所有子集 | #### 四、算法步骤(以经典问题为例) **问题:旅行商问题(TSP)** 求访所有城市(每个城市仅一次)并返回起点的最短路径 1. **态定义** $dp[state][i]$:当前已访城市集合为 $state$(缩整数),最后位于城市 $i$ 的最短路径 2. **态转移** $$ dp[state][i] = \min_{j \in state} \{ dp[state \setminus \{i\}][j] + dist[j][i] \} $$ 其中 $state \setminus \{i\}$ 用位运算实现:`state ^ (1<<i)` 3. **初始化** - 起点:$dp[1<<0][0] = 0$(从城市0出发) - 其他态初始化为无穷大 4. **结果提取** $$ \min_{i} \{ dp[full\_state][i] + dist[i][0] \} $$ $full\_state = (1<<n)-1$(所有城市已访) #### 五、代码实现(TSP问题) ```python def tsp(dist): n = len(dist) total_states = 1 << n # 态总数 2^n dp = [[float('inf')] * n for _ in range(total_states)] # 初始化:从0号城市出发 dp[1 << 0][0] = 0 for state in range(total_states): for i in range(n): if not (state & (1 << i)): # 跳过未访城市 continue for j in range(n): if state & (1 << j): # j必须在已访集合中 prev_state = state ^ (1 << i) # 移除当前城市 dp[state][i] = min(dp[state][i], dp[prev_state][j] + dist[j][i]) # 返回最终结果(回到起点0) full_state = (1 << n) - 1 return min(dp[full_state][i] + dist[i][0] for i in range(n)) ``` #### 六、典型应用场景 1. **棋盘覆盖问题** - 如炮兵布阵(引用[4]):每个位置/不炮兵 → 二进制位表示 - 约束:同行炮兵间距≥2 → 位运算检查 `state & (state>>1)` 或 `state & (state>>2)` 2. **子集选择问题** - 如背包变种:选择物品组合满足特定条件 - 态:$dp[mask]$ 表示选中物品集合为 $mask$ 的最优解 3. **路径覆盖问题** - 如最小权路径覆盖:用二进制表示已覆盖节点 #### 七、优化技巧 1. **滚动数组**:空间优化(如 $dp[state]$ 只依赖 $dp[state']$) 2. **预处理合法**:提前计算满足约束的态集合 3. **剪枝**:跳过无效态(如 `state` 中不包含必要元素时) #### 八、复杂度分析 - **时间复杂度**:$O(2^n \times n^2)$(TSP为例) - **空间复杂度**:$O(2^n \times n)$ 当 $n>20$ 时需谨慎使用(引用[3]) > DP通过将高维缩为整数,显著降低了态空间维度,是解决小规模组合优化问题的利器(引用[1][2])。其核心在于**用位运算实现集合操作**,将复杂的集合关系转化为整数运算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值