状压Dp初学

第一次学习状压DP,感觉就是Dp的优化版本,把存不下但是好表示的状态用数字表示一下,

对所有的约束条件也用数表示,例如

T1:

问题描述20)

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

输入格式

一行,若干个1到20之间的整数。

输出格式

若干行,对于输入的每个整数,输出其方案数。

样例输入

3 2

样例输出

6
2

限制与约定

时间限制:1s

空间限制:128MB

数学方法:A(n,n)

状压DP法:

设f[i]表示哪些行放置旗子的方案数,不考虑顺序,

所以

f[0]=1;f[001]=f[000];f[011]=f[001]+f[010];以此类推,表示的是,在第j行多放一个

是从 不放这行的 方案数 转移过来的

Code:

#include<cstdio>
#define LL long long 
using namespace std;
const int maxn=(1<<21)-1;
LL f[maxn];
int lowbit[maxn];
int main()
{
    int n;
    for(int i=1;i<=maxn;i++)lowbit[i]=i&(-i);//预处理实际上意义不大
    f[0]=1;//注意边界条件
    for(int i=1;i<=maxn;i++){//使用2进制枚举,复杂度O(n^2)
        int j=i;
        
        while(j){
            f[i]+=f[i-lowbit[j]];//每次去掉一位
            j-=lowbit[j];    
        }
    }
    while(scanf("%d",&n)==1){
    printf("%lld\n",f[(1<<n)-1]);
    }
    return 0;
}

 

496. 棋盘问题2

问题描述 n×n(n20)

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

输入格式

给你一个n和m,分别表示棋盘的大小,不能放的格子总数。接下来是m行坐标,表示不能放置的位子。

输出格式

符合条件的方案总数。

输入样例1

3 2 
1 2 
2 3 

输出样例1

3

输入样例2

4 1
1 1 

输出样例2

18

限制与约定

时间限制:1s

空间限制:128MB

多了一个限制条件,我们也把他压缩

 

#define LL long long 
#include<cstdio>
using namespace std;
int n,m,a,b;
LL f[1<<21];
bool ban[1<<21][21];
inline int getone(int n){
    int ans=0;
    while(n){
        if(n&1)ans++;
        n>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--){
        scanf("%d%d",&a,&b);
        ban[1<<(a-1)][b]=1;    
    }
    f[0]=1;
    for(int i=1;i< 1<<n;i++){
        
        int j=i,cnt=getone(i);//只有该行不能转移方案,其他行可以
        while(j){
            int lowbit=j&-j;
            if( !ban[lowbit][cnt] )f[i]+=f[i-lowbit];
            
            j-=lowbit;
        }
    }
    printf("%lld\n",f[(1<<n)-1]);
    return 0;
}

 or:
更加节省空间:

#define LL long long 
#include<cstdio>
using namespace std;
int n,m,a,b;
LL f[1<<21];
LL ban[21];
inline int getone(int n){
    int ans=0;
    while(n){
        if(n&1)ans++;
        n>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--){
        scanf("%d%d",&a,&b);
        ban[b]|=1<<(a-1);
    }
    f[0]=1;
    for(int i=1;i< 1<<n;i++){
        
        int j=i,cnt=getone(i);
        while(j){
            int lowbit=j&-j;
            if(! (ban[cnt]&lowbit) )f[i]+=f[i-lowbit];
            
            j-=lowbit;
        }
    }
    printf("%lld\n",f[(1<<n)-1]);
    return 0;
}

497. 棋盘问题3(good question)

问题描述

给出一个n×m

的棋盘(nm80,n×m80

),要在棋盘上放k(k≤20)个棋子, 使得任意两个棋子不相邻(相邻指上下左右四个方向)。

求可以放置的总的方案数。

输入格式

一行三个整数n,m,k,表示棋盘大小为n行m列,棋盘上放置k个棋子。

输出格式

一个整数表示方案数。

输入样例1

 3 3 2  

输出样例1

 24 

输入样例2

 2 2 2 

输出样例2

 2 

输入样例3

 20 1 2 

输出样例3

 171 

限制与约定

时间限制:1s

空间限制:128MB

分析:

 本题第一眼看起来像f[i][j][k][b]的Dp,但是没敢写因为巨麻烦,转移方程非常繁琐,
但是状压DP直接压得话,肯定会爆空间得
那我们怎么压,压什么
我们考虑优化,nm<=80,算数原理,n和m之间一定有一个<10的数,这就可以用2进制压缩了
(并不一定压k,压不了)
因为每一行都有关系,而且该行符合条件的状态长得都一样,
所以我们预处理出来一个存2合法状态的数组,
节省空间时间,
我们设计状态,
肯定有一维是行数,然后肯定有一维 是状压的二进制数,然后还应该有一维是当前用的旗子数,
状压什么?
压我们用起来最方便的,该行的排列,状态数<=2^9 并且能和上一行状态转移
于是思路就出来了
Code is as follow

 

AC:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
int n,m,k,temp;
LL f[100][100][21],s[100],one[100];//行与行之间是相互影响的 
//f[i][j][k]表示i行,j列排列情况,k个旗子的方案数 

void dfs(int dep,int num,int cnt){
    if(dep==n+1){
    s[++temp]=num>>1;//一开始就空出来1位 
    one[temp]=cnt;
    return;
    }
    else{
        if( ! (num& 1<<(dep-1)) )dfs(dep+1,num|(1<<dep),cnt+1);
        dfs(dep+1,num,cnt);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    if(n>m)swap(n,m);//n<=m -> n<10
    dfs(1,0,0);
    f[0][temp][0]=1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=temp;j++)
        
        for(int v=one[j];v<=k;v++)
        for(int p=1;p<=temp;p++){
            if( !(s[j]&s[p]) )f[i][j][v]+=f[i-1][p][v-one[j]];
        }
    }
    LL ans=0ll;
    for(int i=1;i<=temp;i++){
        ans+=f[m][i][k];
    }
    printf("%lld",ans);
    return 0;
}

 

498. 棋盘问题4

问题描述

n×n(n10)

的棋盘上放k 个国王(可攻击相邻的8 个格子),求使它们无法互相攻击的方案数。

输入格式

一行两个整数n,k,棋盘大小为n行n列,棋盘上放置k个棋子。

输出格式

一个整数表示方案数。

输入样例1

 2 1 

输出样例1

 4

输入样例2

 3 2 

输出样例2

 16

限制与约定

时间限制:1s

空间限制:128MB

没有任何区别,只是判断时加了一个条件:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
int n,k,temp;
LL f[15][1025][105],s[1025],one[1025];//行与行之间是相互影响的 
//f[i][j][k]表示i行,j列排列情况,k个旗子的方案数 

void dfs(int dep,int num,int cnt){
    if(dep==n+1){
    s[++temp]=num>>1;//一开始就空出来1位 
    one[temp]=cnt;
    return;
    }
    else{
        if( ! (num& 1<<(dep-1)) )dfs(dep+1,num|(1<<dep),cnt+1);
        dfs(dep+1,num,cnt);
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    //if(n>m)swap(n,m);//n<=m -> n<10
    dfs(1,0,0);
    f[0][temp][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=temp;j++)
        
        for(int v=one[j];v<=k;v++)
        for(int p=1;p<=temp;p++){
            if( !(s[j]&s[p]) && !((s[j]<<1)&s[p]) && !((s[j]>>1)&s[p]) )
f[i][j][v]+=f[i-1][p][v-one[j]]; } } LL ans=0ll; for(int i=1;i<=temp;i++){ ans+=f[n][i][k]; } printf("%lld",ans); return 0; }

500. 棋盘问题6

问题描述

给出n×m

(1≤n、m≤11)的方格棋盘,用1×2

的长方形骨牌不重叠地覆盖这个棋盘,求覆盖满的方案数。

输入格式

第一行两个整数n,m,表示棋盘大小为n行m列。

输出格式

一个整数表示方案数。

输入样例

2 4

输出样例

5

限制与约定

时间限制:1s

空间限制:128MB

这个问题一开始没想到,但是经过分析之后发现确实可以状压

我们设每一行的状态为(2)0001001000...

其中0表示使用横向牌 或者 被上一次的竖向牌覆盖

(或者表示将被下一次的竖向牌覆盖,都一样的)

1表示使用放置竖向牌的开头

然后我们设计状态转移方程:

f[ i ][ ( 2 ) j ]=sum(f[ i-1 ][ ( 2 ) k ]) ,j|k==0 and  j &k合法

然后计算一下复杂度,可以接受

Code:

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
int n,m,situ[1<<12],cnt;
LL f[15][1<<12];
//f[i][(2)0101010101]表示第i行 0->一层,1->两层,注意两个1之间至少2个0 bool True[1<<12];
void dfs_True(int dep,int ans){
    if(dep==n+1){
        True[ans]=1;
        return;
    }
    if(dep>n)return;
    dfs_True(dep+1,ans|1<<(dep-1));
    dfs_True(dep+2,ans);
}
int main(){
    scanf("%d%d",&n,&m);
    if(n>m)swap(n,m);
    dfs(1,0,-10);
    dfs_True(1,0);
    f[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) && True[ j | k ])
                f[i][ j ]+=f[i-1][ k ];   
            }
        }
    }
    printf("%lld",f[m][0]);//注意最后的答案长什么样子
    return 0;
}

!!!503. 炮兵阵地(noi2001)

问题描述

司令部的将军们打算在N×M

的网格地图上部署他们的炮兵部队。一个N×M

的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

图片无效

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入文件

文件的第一行包含两个由空格分割开的正整数,分别表示N和M;

接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。

N≤100;M≤10。

输出文件

文件仅在第一行包含一个整数K,表示最多能摆放的炮兵部队的数量。

输入样例

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

输出样例

6

限制与约定

时间限制:1s

空间限制:128MB

省选题???

我们可以发现这可以状压,但是状态量极大,而且转移起来涉及前两行的状态

那我们考虑优化

首先,要想满足限制,该行就首先要符合限制,我们预处理出合法状态的数组,因为很难初始化

第0行也就是边界,所以我们直接在dfs中初始化

然后,考虑是否可以枚举两层,前两行的状态,然后捏在一起给当前行,发现

理论上可以,但是,上一行的状态包括了上两行的状态,所以我们还得容斥去重。。。

非常繁琐,再根据这题只有 120/每层for 的复杂度,我们大胆的将状态 拓展到3维

f[i][(2)j][(2)k]表示第i行,该行是  j 的状态,上一行是  k  的状态 的最优解

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
int n,m,c[200],s[200],ban[105],temp;
int f[105][200][200];
void dfs(int dep,int ans,int cnt){
    if(dep>m){
        s[++temp]=ans;
        c[temp]=cnt;
        if(!(ans&ban[1]))f[1][temp][1]=cnt;//直接初始化f[1]
        return;
    }
    dfs(dep+1,ans,cnt);
    dfs(dep+3,ans|1<<(dep-1),cnt+1);
}
int main(){
    scanf("%d%d",&n,&m);
    memset(f,0xf1,sizeof(f));//Dp初值
    
    dfs(1,0,0);
    
    char x[15];
    
    for(int i=1;i<=n;i++){
        scanf("%s",x+1);
        for(int j=1;j<=m;j++){
            if(x[j]=='H'){
                ban[i]|=1<<(j-1);// 限制条件也状压
            }
        }
    }
    int ans=0;
    
    
    for(int i=2;i<=n;i++){
        for(int j=1;j<=temp;j++){
            if(s[j]&ban[i])continue;
            for(int k=1;k<=temp;k++){//上一行 
                if(s[j]&s[k] || s[k]&ban[i-1])continue;
                for(int p=1;p<=temp;p++){//上2行
                    if(s[p]&ban[i-2] || s[p]&s[j] || s[p]&s[k])continue;
                    
                    f[i][j][k]=max(f[i-1][k][p]+c[j],f[i][j][k]);
                    if(i==n)ans=max(ans,f[i][j][k]); // 便于统计答案,减少点时间复杂度
                }
            }
        }    
    }
    printf("%d",ans);
    return 0;
}

 

题目描述

在一个N行M列的棋盘上,放若干个炮(可以不放),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。

在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子

输入输出格式

输入格式:

一行包含两个整数N,M,之间由一个空格隔开。

输出格式:

总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。

输入输出样例

输入样例#1:

1 3

输出样例#1:

7

说明

样例说明

除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有2*2*2-1=7种方案。

数据范围

100%的数据中N和M均不超过100

50%的数据中N和M至少有一个数不超过8

30%的数据中N和M均不超过6

???

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF =1e7;
int ans,n,f[16][2<<16],log[2<<16];
//f[i][(2)j]表示当前走到第i个城市,已经过 j 城市的最短路径 
int main(){
    scanf("%d",&n);
    memset(f,0x3f,sizeof(f));
    ans=INF;
    //printf("%d\n",(1<<n)-1);
    for(int i=0;i<=n;i++){//log(1)=1;
        log[1<<i]=i+1;
    }
    
    int x;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&x);
            if(x)f[j][ 1<<(j-1) | 1<<(i-1) ]= x;
        }
    }
    
    for(int i=2;i<=n;i++){
        for(int j=1;j<(1<<n);j+=2){
            if(j & 1<<(i-1))continue;
            int target=j|1<<(i-1);
            int k=j-1;
            
            while(k){
                int lowbit=k&(-k);
                
                int p=log[lowbit];
                f[i][target]=min(f[i][target],f[p][j]+f[i][lowbit|1<<(i-1)]);
                k-=lowbit;
            }
            //printf("%d %d %d\n",i,target,f[i][target]);
        }
    ans=min(ans,f[i][(1<<n)-1]+f[1][1|1<<(i-1)]);
    }
    printf("%d",ans);
    return 0;
}

 n个物品 每个物品m个属性,不同的排列,相邻两数差的绝对值的最小值最大,

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstdlib>
#define LL long long
using namespace std;
template<typename T>
inline void read(T &a){
    a=0;bool b=0;char x=getchar();
    while(x<'0'||'9'<x){
        if(x=='-')b=1;
        x=getchar();
    }
    while('0'<=x&&x<='9'){
        a=(a<<1)+(a<<3)+x-'0'; 
        x=getchar();
    }
    if(b)a=-a;
}
char C_[50];
int TEMP;
template<typename T>
inline void write(T a){
    if(a<0){
        a=-a;
        putchar('-');
    }
    do{
        C_[++TEMP]=a%10+'0';
        a/=10;    
    }while(a);
    while(TEMP)putchar(C_[TEMP--]);
}
//最小值最大:二分  or DP(其实可以)
const int maxn=17;
const int maxm=1e4+5;
const int INF=1e7;
int ans,n,m,a[maxn+1][maxm],f[maxn][maxn][10+(1<<16)];
int g[20][20][2],flog[(1<<maxn)+10];
int main(){
    //freopen("1.in","r",stdin);
    
    read(n);read(m);
    for(int i=0;i<n;i++)flog[1<<i]=i+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            read(a[i][j]);
        }
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
        if(i==j)continue;
        g[i][j][0]=g[i][j][1]=INF;
        
        for(int k=1;k<=m;k++){
        g[i][j][0]=min(g[i][j][0],abs(a[i][k]-a[j][k]));
        if(k<m)g[i][j][1]=min(g[i][j][1],abs(a[j][k]-a[i][k+1]));
        }
        f[i][j][(1<<(i-1)) | (1<<(j-1))]=g[i][j][0];
        //printf("%d %d %d\n",i,j,f[i][j][1<<(i-1) | 1<<(j-1)]);
    }
    
    for(int s=1;s< (1<<n);s++){//枚举可选状态 
        /*
        for(int s=0;s< (1<<n);s++)
        当n==2 时 会出错,
        注意状压DP的答案更新
        */
        //printf("%d:\n",s);
        
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(i==j || s&(1<<(i-1))  || s&(1<<(j-1)))continue;
            //printf("--------%d %d ",i,j);
            int target=s + (1<<(i-1)) + (1<<(j-1));
            int k=s;
            
            while(k){
                int lowbit=k&(-k);
                int pos=flog[lowbit];
                
                f[i][j][target]=max(f[i][j][target],min(f[i][pos][target-(1<<(j-1))],f[pos][j][lowbit+(1<<(j-1))]));
                k-=lowbit;
            }
            //printf("%d\n",f[i][j][target]);
            if(target+1==(1<<n))ans=max(ans,min(f[i][j][target],g[i][j][1]));
        }
    }
    printf("%d",ans);
    return 0;
}

 

posted @ 2019-07-23 15:07  Tj1  阅读(210)  评论(0编辑  收藏  举报