• 状压dp专题


    经典的状压dp

    先考虑横着放 如果横着放的方案确定了 那么竖着放的也就唯一确定了

    所以总方案数=横着放的方案数

    但是可能我们横着放完了后 留下的空间竖着放怎么都不能放满(也就是竖着连续对的0为奇数)不合法

    这个我们可以预处理

    定义方程:设dp[i,j]表示前i列已经放完横木块且第i列的状态为j的总方案数

    例如j=010110 则表示第二,四,五行有木块捅到后面一列去(也就是横着放的木块的头子在第i列的第2,4,5行)

    转移方程:dp[i,j]+=dp[i-1,k] 其中j和k状态必须合法

    合法条件:1, j&k=0 因为防止木块重合
    2, 第i列合法(第i列的木块包括第i-1列捅过来的和第i列捅出去的)
    初始状态dp[0,0]=1
    终止状态dp[m,0](第m列不能再往后捅了)

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) x&(-x)
    #define ll long long
    const int maxn=12;
    int n,m;
    ll dp[maxn][1<<(maxn-1)];
    int pd[1<<(maxn-1)];
    int main(){
    	cin>>n>>m;
    	while(n!=0&&m!=0){
    		for(int i=0;i<1<<n;i++){
    			int cnt=0;
    			pd[i]=true;
    			for(int j=0;j<n;j++){
    				if((i>>j)&1){
    					if(cnt&1){
    						pd[i]=false;
    					}else cnt=0;
    				}else cnt++;
    			}
    			if(cnt&1)pd[i]=false;
    		}
    		memset(dp,0,sizeof(dp));
    		dp[0][0]=1;
    		for(int i=1;i<=m;i++){
    			for(int j=0;j<(1<<n);j++){
    				for(int k=0;k<(1<<n);k++){
    					if(!(j&k)&&pd[j|k])
    					dp[i][j]+=dp[i-1][k];
    				}
    			}
    		}
    		cout<<dp[m][0]<<endl;
    		cin>>n>>m;
    	}
         return 0;
    }
    

    这个也是很经典的一道状压dp 比上面那道要简单

    设dp[i,j]表示已经走过的城市状态为i,且最后一个城市为j

    初始状态 dp[1,0]=0

    终止状态 dp[(1<<n)-1,n-1]

    转移方程 dp[i,j]=min(dp[i,j],dp[i-(1<<j),k]+w[k][j]) 其中j和k为i状态里面互不相同的城市

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) x&(-x)
    #define ll long long
    #define inf 1e9
    const int N=20;
    const int M=1<<19;
    int dp[M][N],w[N][N];
    int n;
    int main(){
    	cin>>n;
    	for(int i=0;i<n;i++)
    	for(int j=0;j<n;j++)
    	cin>>w[i][j];
    	memset(dp,0x3f,sizeof(dp));
    	dp[1][0]=0;
    	for(int i=0;i<1<<n;i++)
    		for(int j=0;j<n;j++)
    		if((i>>j)&1)
    		for(int k=0;k<n;k++)
    		if(((i>>k)&1)&&k!=j)
    		dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[j][k]);
    		cout<<dp[(1<<n)-1][n-1];
         return 0;
    }
    /*
    5
    0 2 4 5 1
    2 0 6 5 3
    4 6 0 8 3
    5 5 8 0 5
    1 3 3 5 0
    */
    
    

    感觉这个题目放在这个专题多不好 因为正解是肯定不能用状压dp的

    可以用状压 但是一定不能用dp

    因为这个题目无法保证无后效性 简而言之

    如果我们从大到小开始枚举状态 此时状态为 i 在按下一个按钮后状态为 j

    此时 j可能大于i可能小于i 这样我们从大到小开始枚举状态的意义何在?

    出这个题目的人想法很好 但是对dp理解还不够深刻

    放出状压dp的code(错的但是能过)

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) x&(-x)
    #define ll long long
    const int maxn=10;
    const int maxm=105;
    int n,m;
    int a[maxm][maxn];
    int dp[1<<(maxn+1)];
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=m;i++)
    	for(int j=0;j<n;j++)
    	cin>>a[i][j];
    	memset(dp,0x7f,sizeof(dp));
    	dp[(1<<n)-1]=0;
    	for(int i=(1<<n)-1;i>=0;i--){
    		for(int num=1;num<=m;num++){
    		int j=i;
    			for(int k=0;k<n;k++){
    				if(a[num][k]==1){
    			if((i>>k)&1)j-=(1<<k);
    		}else if(a[num][k]==-1){
    			if(!((i>>k)&1))j+=(1<<k);
    		}
    			}
    		dp[j]=min(dp[j],dp[i]+1);
    		}
    	}
    	if(dp[0]!=2139062143)
    	cout<<dp[0]<<endl;
    	else cout<<"-1"<<endl;
         return 0;
    }
    
    

    正解就只能是bfs+状压

    正确的code:

    点击查看代码
    #include <iostream>
    #include <cstdio>
    #include <queue>
    using namespace std;
    
    int n, m;
    int a[110][15];
    bool vis[2000];
    int step[2000];
    queue<int> q;
    
    int main() {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= m; ++i) {
    		for (int j = 1; j <= n; ++j) {
    			scanf("%d", &a[i][j]);
    		}
    	}
    	q.push((1 << n) - 1);
    	vis[(1 << n) - 1] = true;
    	while (q.size()) {
    		int tx = q.front(); q.pop();
    		if (!tx) { printf("%d", step[tx]); return 0; }
    		for (int i = 1; i <= m; ++i) {
    			int ttx = tx;
    			for (int j = 1; j <= n; ++j) {
    				if (a[i][j] == 1 && (tx & (1 << j - 1))) ttx &= ~(1 << (j - 1));//此处判断 ttx & (1 << j - 1) 亦可,因为当前位置 j 的值并未被修改 
    				if (a[i][j] == -1 && !(tx & (1 << j - 1))) ttx |= 1 << (j - 1);
    			}
    			if (!vis[ttx]) q.push(ttx), vis[ttx] = true, step[ttx] = step[tx] + 1;
    		}
    	}
    	printf("-1");
    	return 0;
    }
    

    首先一看数据 状压dp跑不掉了

    代码是w[i,j]是把i 全部放在 j 后面需要的步数 两种是一样的

    点击查看代码
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <cmath>
    #include <climits>
    #include <cstdlib>
    using namespace std;
    const int MAXN = 4e5 + 3;
    int n , a[MAXN] ;
    long long w[23][23];
    long long dp[1<<20+2];
    long long cnt[MAXN];
    int main(){
        scanf( "%d" , &n );
        for( int i = 1 ; i <= n ; i ++ ){
            scanf( "%d" , &a[i] );cnt[a[i]-1] ++;
            for( int j = 0; j < 20 ; j ++ )
                w[j][a[i]-1] += cnt[j];
        }
        dp[0] = 0;
        for( int i = 1 ; i < ( 1 << 20 ) ; i ++ ){
            dp[i] = LLONG_MAX;
            for( int j = 0 ; j < 20 ; j ++ ){
                if( i & ( 1 << j ) ){
                    int k = i ^ ( 1 << j );
                    long long sum =0 ;
                    for( int l = 0 ; l < 20 ; l ++ ){
                        if( l != j && ( k & ( 1 << l ) ) ){
                            sum += w[j][l];
                        }
                    }
                    dp[i] = min( dp[i] , dp[k] + sum );
                }
            }
        }
        printf( "%lld" , dp[(1<<20)-1] );
    }
    

    本来找网络流24题的 但是发现这个题直接一个状压 +最短路就好了

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) x&(-x)
    #define ll long long
    const int maxn=22;
    int n,m;
    int vis[(1<<maxn)],a[maxn*10],b[maxn*10],c[maxn*10],d[maxn*10];
    int dis[(1<<maxn)],val[maxn];
    string s; 
    void spfa(){
    	queue<int>Q;
    	memset(dis,0x7f,sizeof(dis));
    	dis[(1<<n)-1]=0;
    	Q.push((1<<n)-1);
    	while(!Q.empty()){
    		int u=Q.front();
    		Q.pop();vis[u]=0;
    		for(int i=1;i<=m;i++)
    			if((u&a[i])==a[i]&&(u&b[i])==0){
    			int to=((u|c[i])|d[i])^c[i];
    			if(dis[to]>dis[u]+val[i]){
    				dis[to]=dis[u]+val[i];
    				if(!vis[to]){
    					vis[to]=1;
    					Q.push(to);
    				}
    			}	
    			}
    	}
    	if(dis[0]==dis[(1<<maxn)-1])
    	cout<<0<<endl;
    	else 
    	cout<<dis[0]<<endl;
    }
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=m;i++){
    		cin>>val[i]>>s;
    		for(int j=0;j<n;j++)
    		if(s[j]=='+')a[i]|=(1<<j);
    		else if(s[j]=='-')b[i]|=(1<<j);
    		cin>>s;
    		for(int j=0;j<n;j++)
    		if(s[j]=='-')c[i]|=(1<<j);
    		else if(s[j]=='+')d[i]|=(1<<j);
    	}
    	spfa();
         return 0;
    }
    
  • 相关阅读:
    LeetCode-102-二叉树的层序遍历
    LeetCode-086-分隔链表
    LeetCode-082-删除排序链表中的重复元素 II
    LeetCode-081-搜索旋转排序数组 II
    [leetcode]92. Reverse Linked List II反转链表2
    [leetcode]94. Binary Tree Inorder Traversal二叉树中序遍历
    [leetcode]100. Same Tree相同的树
    [leetcode]54. Spiral Matrix螺旋矩阵
    [leetcode]58. Length of Last Word最后一个词的长度
    [leetcode]41. First Missing Positive第一个未出现的正数
  • 原文地址:https://www.cnblogs.com/wzxbeliever/p/16112579.html
Copyright © 2020-2023  润新知