• 洛谷P1896[SCOI2005]互不侵犯(状压DP)


    题目描述

    在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

    注:数据有加强(2018/4/25)

    输入输出格式

    输入格式:

    只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

    输出格式:

    所得的方案数

    输入输出样例

    输入样例#1:

     3 2
    

    输出样例#1:

     16
    


    (Solution:)

    (n<=9),所以我们可以用状压DP解决这题

    由于当前状态只会对下一行造成影响,所以DP方程中要记一下这个状态,状压一下就好了;另外你只能放 (k) 个国王,所以你还需要一维来记录当前放的国王总数。

    首先我们可以处理出每一行所有可能的状态,记下状态并记下放了多少国王,这个可以用DFS实现:

    void dfs(int p,int v,int c){
        if(p>=n)
        {	
            //h[i]数组表示第i种可能的情况中放了多少国王
            //g[i]表示第i种可能的情况的状态
            h[++cnt]=v,g[cnt]=c;
            return;
        }//这里我把可能状态的总数用cnt记录了下来
        dfs(p+1,v,c);//如果不在p号位置放国王,就继续找
        dfs(p+2,v+1,c+(1<<p));//如果在p号位置放,那么p+1号位置就放不了了
    }
    

    (f[i][j][k]) 表示已经到了第 (i) 行,总共放了 (j) 个国王,且当前状态为(k) 种可能状态的方案数。转移方程就是:

    for(int i=1;i<=cnt;i++) f[1][h[i]][i]=1;//初始化第一行
    for(int i=2;i<=n;i++)
        for(int j=1;j<=cnt;j++)//枚举可能的状态编号
            for(int k=1;k<=cnt;k++)//同上
            {
                if(g[j]&g[k]) continue;
                if(g[j]&(g[k]>>1)||g[j]&(g[k]<<1))
                    continue;//判断合法性
                for(int p=m;p>=h[j];p--)//转移
                    f[i][p][j]+=f[i-1][p-h[j]][k];
            }
    

    最后附上总代码:

    #include<bits/stdc++.h>
    #define il inline
    #define int long long//这题要开long long
    using namespace std;
    const int N=1100;
    
    il int read(){
        int f=1,w=0;char c=0;
        while(!isdigit(c))
        {
            if(c=='-') f=-1;
            c=getchar();
        }
        while(isdigit(c)) w=w*10+(c^48),c=getchar();
        return f*w;
    }
    
    int n,m,cnt,h[N],g[N],f[10][100][N],ans;
    
    void dfs(int p,int v,int c){
        if(p>=n)
        {
            h[++cnt]=v,g[cnt]=c;
            return;
        }
        dfs(p+1,v,c);
        dfs(p+2,v+1,c+(1<<p));
    }
    
    main(){
        n=read(),m=read();
        dfs(0,0,0);//处理每一行的可能的状态
        for(int i=1;i<=cnt;i++) f[1][h[i]][i]=1;
        for(int i=2;i<=n;i++)
            for(int j=1;j<=cnt;j++)
                for(int k=1;k<=cnt;k++)
                {
                    if(g[j]&g[k]) continue;
                    if(g[j]&(g[k]>>1)||g[j]&(g[k]<<1))
                        continue;
                    for(int p=m;p>=h[j];p--)
                        f[i][p][j]+=f[i-1][p-h[j]][k];
                }
        //DP过程
        for(int i=1;i<=cnt;i++) ans+=f[n][m][i];//计算答案
        cout<<ans<<endl;
        return 0;
    }
    
    
  • 相关阅读:
    js正则表达式
    js遍历对象属性
    C# 带Cookies发送请求
    C# Cookies设置和读取
    C# ref 和 out 的使用
    jq 禁用复选框 和输入框
    C++学习之嵌套类和局部类
    C++学习之this指针
    C++学习之运算符重载的总结
    Labview中引用,属性节点,局部变量之间的区别
  • 原文地址:https://www.cnblogs.com/ajy-shi-cj-zui-cai/p/10387904.html
Copyright © 2020-2023  润新知