算法(九) 状压dp

思路

对于状态多,决策较少(一般是两个)的问题,我们可以利用二进制去表示其状态,很多棋盘问题都会用到状压,一般求解会用到搜索

P1879 [USACO06NOV]Corn Fields G

思路

首先值为1的地方可以种草,同时种草的地方满足上下左右无相邻\

令f[i][j]表示第i行状态为j时的答案数

那我们用init[]存放每一行的地图,若j为当前状态,要满足( j & init[] ) == j

预处理同一行满足条件的状态 legal[i],其中(i & (i << 1) ) == 0 && ( i & (i >> 1) ) == 0。(无相邻

那么我们在求第i行时,暴力枚举所有可能情况,令j为i行状态,则j = 0 to maxn,然后对于每个j,r若满足(j & init[i]) == j && legal[j],现在满足了在1上面种草并且同一行无相邻, 我们去枚举i - 1行的情况,令k为i - 1行的状态,k = 0 to maxn,若 !(j & k) ,则该状态满足条件。找到所有的满足条件的k,则f[i][j] = Sigma f[i-1][k](很好理解,然后j 跑完,该行处理完成了,进入下一行

注意,最后答案是Sigma f[n][i]

代码

#include<bits/stdc++.h>
#define Mod (long long)1e8
#define ll long long
using namespace std;
ll n, m, maxn, ans;
ll init[30], legal[30], f[13][1 << 13];
int main(){
    cin >> n >> m;
    for(int i = 1;i <= n ;i ++)
        for(int j = 1; j <= m;j ++){
            ll temp;
            cin >> temp;
            init[i] = (init[i] << 1) + temp;        
        }
    ll maxn = (m << 1) - 1;//全为1
    for(int i = 0;i <= maxn;i ++)
        if( (i & (i << 1)) == 0 && (i & (i >> 1)) == 0 )
            legal[i] = 1;//预处理一行没有相邻的情况
    f[0][0] = 1;
    for(int i = 1; i <= n;i ++)//行
        for(int j = 0;j <= maxn;j ++){//j枚举第i行的情况
            if(legal[j] && (j & init[i]) == j){
                for(int k = 0;k <= maxn;k ++)
                    if(!(k & j))
                        f[i][j] = (f[i][j]%Mod + f[i-1][k] % Mod) % Mod;
            }
        }
    for(int i = 0;i <= maxn;i ++)
        ans = (ans%Mod + f[n][i]%Mod) % Mod;
    cout << ans%Mod;
    return 0;
}   

P1896 [SCOI2005]互不侵犯

这里有一个k个国王的条件,因此需要多开一维

设f[i][j][s]为选到第i行,状态为j时,用了s个国王的方案数

则f[i][j][s]  = Sigma f[i - 1][k][s - sum[j]],sum[]来存每个状态的国王数,num[]存了cnt数量的可行的状态。这里使用预处理提前存好了状态,替换了 for(int j = 0;j <= maxn;j ++)语句

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n, k, maxn, ans, cnt;
ll sum[1 << 11], num[1 << 11], f[20][1 << 11][20];
void init(){
    maxn = (1 << n) - 1;
    for(int i = 0 ;i <= maxn;i ++){
        if(!(i & (i << 1)) && !(i & (i >> 1))){//一行中无相邻
            int s = 0;//统计这个状态的国王数量
            for(int t = 0;(1 << t) <= i;t ++)
                if((1 << t) & i)
                    s ++;
            num[++cnt] = i;//记录成立的状态
            sum[cnt] = s;//该状态所用国王数
        }
    }
}
int main(){
    cin >> n >> k;
    init();
    f[0][0][0] = 1;
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= cnt;j ++){//i的状态为num[j]
            for(int s = 0; s <= k; s ++){
                if(s < sum[j])continue;//s要大于等于此状态要用的棋子数量
                for(int z = 1; z <= cnt; z ++)//枚举i-1的状态num[z]
                    if(!((num[j] << 1) & num[z]) && !((num[j] >> 1) & num[z]) && !(num[j] & num[z]))
                        f[i][num[j]][s] += f[i-1][num[z]][s - sum[j]];       
            }
        }
    }
    for(int i = 1;i <= cnt;i ++)
        ans += f[n][num[i]][k];
    cout << ans;
    return 0;
}   

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值