惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
M
MIT News - Artificial intelligence
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
NISL@THU
NISL@THU
P
Proofpoint News Feed
Microsoft Security Blog
Microsoft Security Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
I
Intezer
Know Your Adversary
Know Your Adversary
T
Threatpost
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
Security Latest
Security Latest
量子位
S
Securelist
Recorded Future
Recorded Future
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
P
Privacy International News Feed
AI
AI
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 司徒正美
博客园_首页
IT之家
IT之家
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
C
CERT Recently Published Vulnerability Notes
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The GitHub Blog
The GitHub Blog
美团技术团队
L
LangChain Blog
F
Fortinet All Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Security Affairs
Cloudbric
Cloudbric
O
OpenAI News
PCI Perspectives
PCI Perspectives
J
Java Code Geeks
P
Privacy & Cybersecurity Law Blog
B
Blog RSS Feed
MyScale Blog
MyScale Blog

博客园 - Mose

CS学习 NOI2002_ Galaxy银河英雄传说86 [Elite 2008 Dec USACO]Jigsaw Puzzles BZOJ 3132(上帝造题的七分钟-树状数组求和+2D逆求和数组) 埃及分数 [usaco]2013-jan Liars and Truth Tellers 真假奶牛 【区间DP】codevs3657 括号序列题解 ubuntu安装php-curl拓展 MIT挑战(如何在12个月内自学完成MIT计算机科学的33门课程|内附MIT公开课程资源和学习顺序 大白话Docker入门(一) Hexo博客搭建全解 代码查重工具sim virtual judge 本地部署方案 POJ题目分类推荐 (很好很有层次感) 解决Ubuntu下Sublime Text 3无法输入中文 [pascal入门]数组 [codecademy]fonts in css [codecademy]css Ubuntu录制gif动态图
SGU 223 little kings BSOJ2772 状压DP
Mose · 2017-05-16 · via 博客园 - Mose

1896 [SCOI2005]互不侵犯King

【问题描述】在n*n(1<=n<=10)的棋盘上放k(0<=k<=n*n)个国王(可攻击相邻的8 个格子),求使它们无法互相攻击的方案总数。

【输入格式】输入有多组方案,每组数据只有一行为两个整数n和k。

【输出格式】每组数据一行为方案总数,若不能够放置则输出0。

【问题分析】

    由问题很容易联想起经典的“八皇后”问题,似乎就是“皇后”变成了“国王”,而且格子范围似乎也差不多,所求问题也一样。那么这个问题也能用搜索解决吗?

    可稍加分析可知搜索是很难胜任的,因为国王的数目可以是很大,加上它与“八皇后”问题的一个本质上的不同便是每个国王只影响周围的一个格子,所以剪枝条件也很少,指数级别的搜索是无法在时限内出解的。

    那么一般的动态规划能解决吗?典型的二维DP,F[I,J]似乎无法很好地把状态表示出来,因此我们只能考虑状态压缩的动态规划。

    首先我们要注意到这题的关键——每个国王只影响周围八个方向的一个格子,它虽然否定了搜索,却给状态压缩带来了无限生机!

    我们改变之前动态规划的思维方式,一行一行地摆放国王,当我们摆放第I行时,这一行只会和前后一行的互相影响,而这一行的状态是可以由我们确定的。那是否可以把一行当作一个整体,然后像传统的动态规划那样进行处理呢?让我们试一下。

    每一行它对下一行的影响就体现在这一行的摆放方式以及之前总共放了多少个国王。所以我们可以把摆放方式作为状态,设f[i,j,s]表示第i行状态为a[j]且前i行已放s个国王的方案总数。这样很容易便得到了一个粗略的方程:

     F[i,j,S]=∑F[i-1,k,T]

    a[j],a[k]分别表示一种摆放方式,F[i,j]表示第i行用a[j]的摆放方式,且a[j]与a[k]相兼容。并且S等于T加上a[j]这种方式在这一行放置的国王数。

    很明显这个方程是没有后效性的,可关键就在于j,k怎么在计算机上表示出来,这就需要我们的主题:状态压缩

    看图便知,每一个格子只有两种状态,放和不放,并且注意到格子宽度最大为9。由这便想到了熟悉的二进制表示法。每一个格子对应一个二进制位。这样每一行便对应一个N位的二进制数,如下图所示:

     1   0   0   0   1   0   0   0

即十进制的128+8=136

    这样我们就可以把一种摆放方式转化为一个数,这样上面方程中的J,K就可以用数字来代替。我们就把一行的摆放方式作为状态,并把它压缩成了一个数!具体的算法流程如下:

①对于每一行,我们通过搜索得出一个合法状态。

②然后再枚举上一行与这一行相容的状态再累加即可,状态用N位的二进制数表示,最大仅为512,所以一个512*9*81的数组就可以了,还可以用滚动数组的技巧。空间是肯定可以承受的。

 而一个粗略的时间复杂度:O(K*N*2^N*2^N),似乎大了点。不过注意到由于国王是不能相邻放置的。所以我们可以用一个f(n)来表示当列数为n时每一行可能的放置总数。则f(n)=f(n-1)+f(n-2)初始值:f(1)=2,f(2)=3。

则f(9)=89。因此最大也才是9*89*89*81≈6000000。是可以承受的。

压缩行,有king是1,没有是0.
判断可行,就是将上一行与这一行按位与,接着左移,右移。
剩下的就是简单的dp了。

#include<iostream>  
#include<cstring>  
#include<cstdio>  
using namespace std;  
const int N=10;  
int n,k,a[10005],b[10005],tp;  
long long f[N][N*N][1<<N],ans;  
//第i行,一共摆放了j个king,第i行的摆放情况是g   
void dfs(int num,int lst,int at,int bt)  
{  
    if(num==n)  
    {  
        ++tp;  
        a[tp]=at;  
        b[tp]=bt;  
        return ;  
    }  
    dfs(num+1,lst,at,bt);  
    if(num-lst>=2)  
    {  
        at|=(1<<num);  
        bt++;  
        dfs(num+1,num,at,bt);  
    }  
}  
int main()  
{  
    scanf("%d%d",&n,&k);  
    dfs(0,-2,0,0);  
    f[0][0][0]=1;  
    int end=(1<<n)-1;  
    for(int i=1;i<=n;i++)  
        for(int j=0;j<=k;j++)  
            for(int g=0;g<=end;g++)  
                if(f[i-1][j][g]>0)  
                    for(int h=1;h<=tp;h++)  
                        if((a[h]&g)==0&&(a[h]&(g<<1))==0&&(a[h]&(g>>1))==0&&j+b[h]<=k)  
                            f[i][j+b[h]][a[h]]+=f[i-1][j][g];  
    for(int i=0;i<=end;i++)  
        ans+=f[n][k][i];  
    printf("%lld\n",ans);  
    return 0;  
}