状态压缩入门

棋盘放车问题

问题描述:

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

问题分析:车子的攻击是直线的上下和左右的。那么我们可以认为是一行一行扫描过来。有dp[i][j]。j表示排列状况

     但是根据24点游戏的经验。我们完全可以直接根据排列状况来划分状态。(状态本身就是答案。)

                   0001  是由 0000得来的。

                            0011  是由 0010 + 0001得来的。

    状态压缩的真正厉害之处不是以物体为刷层。而是以序列来刷层。并且一个循环过去。是从小到大有方向性的刷层。

    (也许我可以用2种方法来写24点游戏)。一般来说还是刷层的方法比较好。。去处法DP。

#include<stdio.h>
#include<string.h>
int dp[30000];
// 8个棋盘 130个 n范围是9个棋盘
int main()
{
    int n,i,j;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        dp[0] = 1; //还没填 对于不摆当然是1啦。

        for(i=1; i<= (1<<n)-1;i++) //n个1,这一层是状态的扫描。
        {
            for(j=i;j>0;j -= (j&-j))
            {
                dp[i] += dp[i-(j&-j)];
        //这个dp 是由当前的数的位置(i-(j&-j))的不同而获得的值
            }
        }
        printf("%d
",dp[(1<<n)-1]);
    }
    return 0;
}
View Code

 对当前行进行判断处理可否进行放置。

抓住论文上的状态转移。用来源法来只考虑相关的。不要想偏!

/*

input:
0 1 0 0
0 1 1 0
1 0 0 1
0 1 0 0

4

0 0 0 1
0 0 0 1
1 0 1 0
1 1 1 0

2

0 0 0 1
0 0 0 1
1 1 1 0
1 1 1 0

 0
 */
#include<stdio.h>
#include<string.h>
int map[5][5];
int A[5];
int one[5];
void init()
{
    int i,j;
    for(i=1;i<=4;i++)
    {
        A[i] = 0;
        for(j=1;j<=4;j++)
        {
            A[i] = (A[i]<<1) + map[i][j];
        }
    }
}
int dp[(1<<4)+1];
void solve()
{
    int i,j,temp,top;
    memset(dp,0,sizeof(dp));
    dp[0] = 1;
    for(i=1;i<1<<4;i++)
    {
        temp = i;
        top = 0;
        while(temp)
        {
            one[++top] = temp&(-temp);
            temp ^= one[top];
        }

        for(j=1;j<=top;j++)
        {
            if((one[j] & A[top])==0)
            {
                dp[i] += dp[i-one[j]];
            }
        }
    }
}
int main()
{
    int i,j;
    for(i=1;i<=4;i++)
    {
        for(j=1;j<=4;j++)
        {
            scanf("%d",&map[i][j]);
        }
    }
    init();
    solve();
    printf("%d
",dp[(1<<4)-1]);
}
View Code
/*
给出一个n*m 的棋盘(n、m≤80,n*m≤80),要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻。
*/
#include<stdio.h>
int num[300];   // 第i个状态的1的个数
int state[300]; //   存储状态用的
int dp[10][1<<8+1][21];//i j k 
int M;// M 位的数
int top;
//state代表着路径

void DFS(int st,int count,int t,bool flag)
{
    if(t==M)
    {
        state[++top] = st;
        num[top] = count;
        return;
    }
    
    DFS(st<<1,count,t+1,true); 
    if(flag)
    {
        DFS(st<<1|1,count+1,t+1,false);
    }
}
//DFS预处理出合法状态
int main()
{
    int i,j,k,q;
    int N,maxn;
    while(scanf("%d%d%d",&N,&M,&maxn)!=EOF)
    {
        top = 0;
        if(N<M) //M比较小
        {
            int temp = N;
            N = M;
            M = N;
        }
        DFS(0,0,-1,false);        
            dp[0][1][0] = 1; 
        //dp中间的那个状态需要注意 变成数组的下标代替了。
        //由于这种方法 所以请保证数组第一个位的状态是 0 
        for(i=1;i<=N;i++)
        {
            for(j=1;j<=top;j++) //当前行的第j个状态
            {
                for(q=1;q<=top;q++) //上一行的第q个状态
                {
                    for(k=num[j];k<=maxn;k++)
                    {
                        if((state[j]&state[q])==0)
                        {
                            dp[i][j][k] += dp[i-1][q][k-num[j]];
                        }    
                    }
                }
            }
        }
        int sum = 0;
        for(i=1;i<=top;i++)
        { 
            sum += dp[N][i][maxn];
        }
        printf("%d
",sum);
    }
}
View Code

 炮兵布阵问题

#include<stdio.h>
#include<string.h>

int num[61]; //同下。
int s[61]; // 合法的状态数。
int A[102];
int dp[102][61][61];
int N,M;
int max(int a,int b)
{
    return a>b?a:b;
}
void init()
{
    memset(A,0,sizeof(A));
    int i,j,state;
    char string[12];
    memset(string,0,sizeof(string));
    for(i=1;i<=N;i++)
    {
        state = 0;
        scanf("%s",string);
        for(j=1;j<=M;j++)
        {
            if(string[j-1]=='P'){state <<= 1;}
            else{state = (state<<1|1);}
        }
        A[i] = state;
    }
}
//存地图
int top; //init = 0
void DFS(int t,int count,int state,int flag) // 0 0 0 0
{
    if(t==M)
    {
        s[++top] = state;
        num[top] = count;
        return;
    }
    DFS(t+1,count,state<<1,flag+1);
    if(flag>=2)
    {
    DFS(t+1,count+1,state<<1|1,0);
    }
}
//存状态 和 数目
/*
解决最多摆放我军的军队。不同状态取max
    思考状态转移方程。
    不用抽取出来1 我得思考一下抽取1 其实是抽取当前行能摆放的状态罢了
    if((s[j]&A[i]==0)&&(s[j]&s[p]==0)&&(s[j]&s[q]==0))
    {
        dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][q]+num[j]);   
    }
    //j是当前行的棋子情况 k是上一行的棋子情况。 q是上上行的情况。
      dp[i][j][k] 若j是非法的状态。那么就为0。别想太多。如果觉得这行不用取的话。那么有0方案可以替代掉。所以我们只考虑当前
      另外我被)坑了。二进制运算符可真是低等级的运算
      另外dp[i][j][k] 维度的设计其实是为了下一个状态。而形成有推导关系的,所以我们在思考维度的时候需要注意了。
 */
void solve()
{
    int i,j,k,q;
    memset(dp,0,sizeof(0));
    for(i=1;i<=N;i++)
    {
        for(j=1;j<=top;j++)
        {
            for(k=1;k<=top;k++)
            {
                for(q=1;q<=top;q++)
                {
                    if((s[j]&s[k])==0&&(s[j]&s[q])==0)
                    {
                        if((s[j]&A[i])==0)
                        {
                        dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][q]+num[j]); 
                        }
                    }
                }
            }
        }
    }

    int maxn;
    maxn = 0;
    for(i=1;i<=top;i++)
    {
        for(j=1;j<=top;j++)
        {
            if(dp[N][i][j]>maxn){maxn = dp[N][i][j];}
        }
    }
    printf("%d
",maxn);
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        top = 0;
        init();
        DFS(0,0,0,2);
        solve();
    }
}


/*
void DFS(int t,int state,int count,bool flag)
{
    if(t==M)
    {
        s[++top] = state;
        num[top] = count;
    }
    DFS(t+1,state<<1,count,1);
    if(flag)
    {
    DFS(t+1,state<<1|1,count+1,0);
    }
}
*/

/*
5 4
PHPP
PPHH
PPPP
PHPP
PHHP

6

3 3
PHP
HPH
PHP

理论上是 3 我却是 4 如果是连下来的话


*/
View Code

 ZOJ 3802

/*#include<stdio.h>
#include<string.h>
int max(int a,int b){return a>b?a:b;}
int getone(int x)
{
    return (x&(-x));
}
/*
 1 2 3  4 5 
 2 4 8 16 32
 一共有最多20个数字。
 20*16 = 320 的和并后最大的数字。 我只用表示到160个。因为单个就是这样
 而160 对应上表。2 -> 1 那么。2^(位数) >160 即8位到达256。


int dp[20][300];
/*


int num[20];
int N;
void solve()
{
    int i,j;
    memset(dp,-1,sizeof(dp));//因为状态规定了。必须是那样的状态其他的是非法的。
    dp[0][0] = 0;
    for(i=1;i<=N;i++)
    {
        for(j=0;j<256;j++) //这个是状态。
        {
            if(dp[i-1][j]!=-1)
            {
                int nownum = num[i];
                int nows = j;
                int nowval = nownum;
                dp[i][j] = max(dp[i][j],dp[i-1][j]); //第一个作用 

                while((getone(nows)==(nownum>>1)))
                {
                    nowval += (nownum<<1);
                    nows ^= getone(nows);   //这里的状态不再只是表示局势。 还表示出了数值。
                    nownum <<= 1;
                }  //合并操作

                if(getone(nows) < (nownum>>1)){nows = 0;}
                
                nows |= (nownum>>1);
                //这个数比最小的大 这个数比最小的小  第2 3 个情况

                dp[i][nows] = max(dp[i][nows],dp[i-1][j]+nowval);
            }
        }
    }
    int maxn;
    maxn = 0;
    for(i=0;i<256;i++)
    {
        if(dp[N][i]>maxn){maxn = dp[N][i];}
    }
    printf("%d
",maxn);
}
int main()
{
    int i;
    while(scanf("%d",&N)!=EOF)
    {
        for(i=1;i<=N;i++)
        {
            scanf("%d",&num[i]);
        }
        solve();
    }
}
View Code
原文地址:https://www.cnblogs.com/Milkor/p/4324006.html