bzoj2669: [cqoi2012]局部极小值 计数Dp 状态压缩Dp与容斥

bzoj2669: [cqoi2012]局部极小值

Description

有一个n行m列的整数矩阵,其中1到nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

Input

输入第一行包含两个整数n和m(1<=n<=4, 1<=m<=7),即行数和列数。以下n行每行m个字符,其中“X”表示局部极小值,“.”表示非局部极小值。

Output

输出仅一行,为可能的矩阵总数除以12345678的余数。

Sample Input

3 2
X.
..
.X

Sample Output

60

分析

计数Dp的一道难题,但是处处是套路。
首先1到nm之间的每个整数恰好出现一次,肯定是从小到大放。
其次,给出所有局部极小值的位置,包含了一个“有且仅有”这些最小值的条件,数据范围又好小。
于是容斥。
想考虑包含若干个极小值的情况,然后一个一个枚举其他最小值存在的情况即可。
然后不难发现最小值最多8个,于是状态压缩。
这题大致的思路已经出来了。
只需要考虑状态压缩的过程。
假设当前状态为 fi,s f i , s 表示放到第 i i 个数,极小值位置情况是s
考虑 i i 最后放的位置。
如果放在某个极小值上面,那就让s让出一个极小值的位置
fi1,tfi,s,t=s{i},is f i − 1 , t → f i , s , t = s − { i } , i ∈ s
如果不放在极小值上面,那么不可放在任何一个还没有被放的极小值的旁边,因为之后放的数肯定比当前的数大。
fi1,smax(cnt[s]i+1,0)fi,s f i − 1 , s ⋅ m a x ( c n t [ s ] − i + 1 , 0 ) → f i , s 其中 cnt[s] c n t [ s ] 为状态 s s 下可以放的位置个数。
时间复杂度是O(Dfs28nm) 其中Dfs是容斥的复杂度,大概10000吧。

代码

/**************************************************************
    Problem: 2669
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:60 ms
    Memory:1352 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int P = 12345678;
int n, m, tim, ret, top, ans, b[4][7], x[8], y[8], bin[9], f[29][512], cnt[512];
bool a[4][7];
bool Paint(int x, int y, bool p) {
    for(int dx = -1; dx <= 1; ++dx)
        for(int dy = -1; dy <= 1; ++dy) {
            int nx = x + dx, ny = y + dy; 
            if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if(p && a[nx][ny]) return false;
            if(!p && b[nx][ny] != tim) b[nx][ny] = tim, ++ret;
        }
    if(!p && b[x][y] != tim) b[x][y] = tim, ++ret;
    return true;
}
void Up(int &x, int y) {x += y; if(x >= P) x -= P; if(x < 0) x += P;}
int Dp() {
    top = 0;
    for(int i = 0;i < n; ++i)
        for(int j = 0;j < m; ++j)
            if(a[i][j]) x[top] = i, y[top++] = j;
    for(int s = 0;s < bin[top]; ++s) {
        ret = 0; ++tim;
        for(int i = 0;i < top; ++i) if(~s & bin[i])
            Paint(x[i], y[i], 0);
        cnt[s] = n * m - ret;
    }
    f[0][0] = 1;
    for(int i = 1;i <= n * m; ++i)
        for(int s = 0;s < bin[top]; ++s) {
            f[i][s] = 1LL * f[i - 1][s] * max(cnt[s] - i + 1, 0) % P;
            for(int j = 0;j < top; ++j) if(s & bin[j]) 
                Up(f[i][s], f[i - 1][s ^ bin[j]]);
        }
    return f[n * m][bin[top] - 1];
}
void Nxt(int,int,int);
void Dfs(int,int,int);
void Dfs(int i, int j, int s) {
    if(i == n) {Up(ans, s * Dp()); return;}
    Nxt(i, j, s); 
    if(!a[i][j] && Paint(i, j, 1)) {
        a[i][j] = 1;
        Nxt(i, j, -s);
        a[i][j] = 0;
    }
}
void Nxt(int i, int j, int s) {
    if(j == m - 1) Dfs(i + 1, 0, s);
    else Dfs(i, j + 1, s);
}
int main() {
    bin[0] = f[0][0] = 1; for(int i = 1;i <= 8; ++i) bin[i] = bin[i - 1] << 1;
    scanf("%d%d", &n, &m); char s[7];
    for(int i = 0;i < n; ++i) {
        scanf("%s", s);
        for(int j = 0; j < m; ++j)
            a[i][j] = s[j] == 'X';
    }
    Dfs(0, 0, 1);
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值