码迷,mamicode.com
首页 > 其他好文 > 详细

hoj 2662 状态压缩dp

时间:2015-05-05 23:47:38      阅读:188      评论:0      收藏:0      [点我收藏+]

标签:

意:
 
给定mxn的棋盘,要往其中放d枚棋子,其中,一枚棋子的上下左右四个位置不能再放棋子。求所有合法的放置状态 ( 0<m,n<=80 , mxn<=80 )
棋盘位置可多达80,爆搜目测超时,我用的状态压缩dp.
 
棋盘中的一个格子,放棋用1,不放用0
 
压缩其中一维,每个状态数用一个二进制数表示. 每个状态都是棋盘一行的放置方法
 
假设,m >= n , 这个递推棋盘就有 m行 ,每行至多有 2^n-1个状态数。其实,没有这么多!按照题意,每个1的上下左右不能有1。对于一行,只知左右,所以把有连续1的状态排除. t为剩下的合法状态数
 
好了,每一行都至多有t个合法状态,为什么 ? 因为这一行虽然左右合法,但是结合上一行未必上下合法. 
 
思路:
 
第i行 的第j个状态 ,都要维护:1. 第i 行取状态j 后,总的棋子数   2. 这个棋子数对应的方法数
存储很简单,每一个状态,使用一个map 存储 1到2的映射就好了
 
问题在于,这些信息我怎么知道 ?
 
假设我们已经知道第i-1的这些信息,对于第i行的状态j  (棋子数为num[j] )和 第i-1行的状态 k,如果他们不冲突 (上下无相邻1),那么它们组合就是可行的.   对于第i-1行的状态k, 取它的map中的一个key-value pair,  k-v.  
 
  if   k + num[j] = d, 那么总方法数ans 加上 v ( 剩余行状态都是确定的,全为0).  不需要再记录了. 
 
  if  k + num[j] > d, 棋子数已经超过d,不需要再记录了
 
  if k + num[j] < d,配合剩余行,棋子数还可能到达d. 第i行的状态j的 key-value pair:k+num[j]-value 的 value 加上 v    
    (为什么不是让value = v呢 ? 因为状态j 还可以和第i-1行的其他所有状态 来判断,还可以增加到达 k+num[j] 这个棋子数的方法数啊 !)
 
那么初始化,第一行的状态p 对应的map怎么初始化? 简单 !
 
if num[p] = d,  ans 加1
if num[p] > d 不记录
if num[p] < d 加入pair:  ( num[p], 1 )  (第一层当然只有一种方法啦 !)
 
程序结束后,ans就是答案了.
 
优化:
 
1.  压缩m,n中最短的那一个维度,另其为n 可以计算,这最短的一维的维度不超过8. ( 10 x 8 = 80 )
   这样一来,最多有 2^8 个状态数,节省压缩空间  技术分享
2.  不需要为m行所有的状态都维护一个map.  因为第i行的状态的map维护只和上一行的有关。所以开一个
  2xt 的二维map数组就好了.   又省了好多空间有木有  技术分享 , 然后,两个map轮换就好了
 
 
C++ 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>

//hoj 2662 状态压缩dp
typedef long long LL;
LL ans,tag;                

int m,n,t,d;
int i,j,k,l;

int st[1<<8], num[1<<8];

/*每个合法状态对应一个cell (map)
  存储(k,v) pair: (当行取该状态后的棋子数,方法数)
  每个cell存储这个状态的k-v pair*/

std::map<int,LL> MAP[2][1<<8];
                              
void Swap(int &a,int &b) {
    a = b+a-(b=a);
}

/*判断数p是否有连续1*/
bool J(int p) {
    return p&(p<<1);
}

/*记录一个合法状态p中1的个数*/
int cnt(int p) {
    l=0;
    while(p) {
        if(p&1) {
            l++;
            p>>=1;
        }
        p>>=1;
    }
    return l;
}

int main() {
    while(~scanf("%d%d%d",&m,&n,&d))
    {
        t=0; 
        ans=0;
        if(m<n) {
            Swap(m,n);
        }

        //记录所有合法的状态及个数
        for(i=0;i<(1<<n);i++)
        {
            if(!J(i)) {
                num[t]=cnt(i);
                st[t++]=i;
            }
        }

        for(i=0;i<2;i++)
            for(j=0;j<t;j++)
                MAP[i][st[j]].clear();

        int up=0,down=1;
        for(i=0;i<t;i++)
        {
            tag=num[i];
            if(tag==d)
                ans++;
            else if(tag<d)
                MAP[up][st[i]][tag]=1;
        }

        for(i=1;i<m;i++)
        {
            for(j=0;j<t;j++)
            {
                for(k=0;k<t;k++)
                {
                    if((st[j]&st[k])==0) {
                        std::map<int,LL>&fa = MAP[up][st[k]];
                        std::map<int,LL>&son= MAP[down][st[j]];
                        for(std::map<int,LL>::iterator it=fa.begin();it!=fa.end();it++)
                        {
                            tag=it->first+num[j];
                            if(tag==d)
                                ans+=it->second;
                            else if(tag<d)
                                son[tag]+=it->second;
                        }
                    }
                }
            }
            /*
                clear up map, swap up map and down map
                因为上一层所有状态的map只对它的下一层有作用
                所以每次内存中只保存上下两层map 就可以节省空间
            */
            for(j=0;j<t;j++)
                MAP[up][st[j]].clear();
            Swap(up,down);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

 

hoj 2662 状态压缩dp

标签:

原文地址:http://www.cnblogs.com/zhangzph/p/4480518.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!