zoukankan      html  css  js  c++  java
  • 状压DP : [USACO06NOV]玉米田

    玉米田

    内存限制:128 MiB
    时间限制:1000 ms
    标准输入输出
    题目类型:传统
    评测方式:文本比较
    题目描述
    农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

    遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

    John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

    输入格式
    第一行:两个整数M和N,用空格隔开。

    第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

    输出格式
    一个整数,即牧场分配总方案数除以100,000,000的余数。

    样例
    样例输入
    2 3
    1 1 1
    0 1 0
    样例输出
    9

    (此题注意方案数要除以余数)

    这道题很容易想到DP,并且是状压DP。为什么呢,设状态(f_{i})为到第i行为止总的方案数,如果我们不能表示出(i-1)行的状态的话,那么第(i)行也推不出来。(第(i)行和第(i-1)行不能有土地邻接)

    于是考虑状态压缩。用一个长度为(m)的二进制数(j)来表示第(i)行选择土地的情况,(j)的第(k)位为1表示在第(i)行的第(k)列选了一个土地。

    于是我们便得到了状态转移方程:
    (f_{i,j} = sum_{f_{i-1,k}})

    那么这篇题解到这里就结束了

    好吧别打我。

    这道题的难点就是在于判断(j),(k),(j)(k)这两个状态是否合法。这里就体现出了位运算的优越性。

    对于判断一行农田(j)是否有相邻的已选的农田,我们可以这样判断:

    if( (j<<1) & j)  不合法
    else 合法
    

    (位运算这篇博客里不会详讲,这种方法的可行性请读者自己翻阅资料思考)

    同理对于状态(k)我们也可以这样判断。

    对于上下相接的两个状态(j)(k),我们要判断它们的每一位是否出现了都是1的情况:

    if(i&j) 不合法
    else 合法
    

    (还有一个居然忘讲了)

    需要注意的是,在枚举(j)的过程中,我们还要判断我们是否选择了“贫瘠”的土地,这是我使用的方法:

    for(int i=1;i<=n;i++)  for(int j=1,L;j<=m;j++) {
            cin>>L;
            P[i]=(P[i]<<1)+L;
    	}//将初始的农田转换成2进制
    	for(int i=0;i<(1<<m);i++)
            if((!((i<<1) & i)) && ((P[1] & i) == i))
                f[1][i]=1; //初始化状态,((P[1] & i)  ==  i)就是我用来判断是否
                			//选择了贫瘠土地的方法.
    

    (关于状压DP,我这里要说一句,熟练掌握位运算是必要条件,这一点可以通过多做题来训练)

    代码

    #include <iostream>
    #include <cstdio>
    #include <vector>
    using namespace std;
    
    #define N 15
    #define LL long long
    #define MOD 100000000
    
    LL n,m,f[15][100010],ans,P[N];
    
    int main() {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)  for(int j=1,L;j<=m;j++) {
            cin>>L;
            P[i]=(P[i]<<1)+L;
    	}
        for(int i=0;i<(1<<m);i++)
            if((!((i<<1) & i)) && ((P[1] & i) == i))
                f[1][i]=1;
        for(int i=2;i<=n;i++)
            for(int y=0;y<(1<<m);y++)
                if((!((y<<1) & y)) && ((P[i] & y) == y))
                    for(int x=0;x<(1<<m);x++)
                        if((!((x<<1) & x)) && ((P[i-1] & x) == x)) {
                            if(x & y) continue;
                            f[i][y]=(f[i][y]+f[i-1][x])%MOD;
                        }
        for(int i=0;i<(1<<m);i++)
            if((!((i<<1) & i)) && ((P[n] & i) == i))
                    ans=(ans+f[n][i])%MOD;
        cout<<ans;
    }
    
    
  • 相关阅读:
    预处理01 零基础入门学习C语言50
    内中断01 零基础入门学习汇编语言60
    预处理03 零基础入门学习C语言52
    预处理02 零基础入门学习C语言51
    对《移动互联网白皮书(2013年)》的几个解读
    Windows Azure Mobile Services增加了对 Android的支持并扩展其适用范围至东亚地区
    中国将自主研发操作系统对抗谷歌
    Windows Azure2.5天免费深度技术训练营——面向软件工程师和架构师
    Windows Azure Store:扩大服务市场
    use Visual studio2012 developing kernel driver monitor thread creation on Windows8
  • 原文地址:https://www.cnblogs.com/MisakaMKT/p/11252270.html
Copyright © 2011-2022 走看看