[BZOJ5010][Fjoi2017]矩阵填数(状压DP)

从最简单的问题开始考虑:
一个面积为 s s 的矩阵,每个位置可以填上[1,m]中的任意一个整数,并且要求这个矩阵的最大值为 v v ,求方案数。
这个问题等价于每个位置填上[1,v]中的任意一个整数,并且至少要有一个 v v
很容易得出答案为vs(v1)s
而如果这 n n 个矩阵两两没有交集,那么可以简单地把这个矩阵分成n+1个部分,分别计算结果之后相乘。
下面就样例中的第 1 1 组数据,讨论有交集的情况。
可以看出,第1组数据中的 2 2 个矩形把矩阵分成了4个部分:
1、 (1,3)(3,1) ( 1 , 3 ) ( 3 , 1 )
2、 (1,1)(1,2)(2,1) ( 1 , 1 ) ( 1 , 2 ) ( 2 , 1 )
3、 (2,3)(3,2)(3,3) ( 2 , 3 ) ( 3 , 2 ) ( 3 , 3 )
4、 (2,2) ( 2 , 2 )
1 1 个部分无限制,每个格子可以填1 2 2 ,方案数为4
4 4 个部分恰好是两个矩形的交集,因此绝对不能填大于1的数,否则第 2 2 个矩形的最大值永远不会等于1。因此 (2,2) ( 2 , 2 ) 只能填 1 1
3个部分只能填 1 1 ,方案数为1
2 2 个部分可以填1 2 2 ,且要至少有一个2,所以方案数为 23(21)3=7 2 3 − ( 2 − 1 ) 3 = 7
综上所述,方案数为 4×1×1×7=28 4 × 1 × 1 × 7 = 28
而在实际上, n n 个矩形可以把矩阵分割成O(2n)块。
于是就有了一个状压DP的模型: f[i,S] f [ i , S ] 表示到了第 i i 块,已经满足限制的子矩形集合为S的方案数。当然,这些块不包括子矩形外面无限制的部分(最后乘上无限制部分的方案数即可)。
首先需要预处理出每块是哪些子矩形的交集,每一块的面积 areai a r e a i ,以及这些子矩形需求的最大值的最小值 val v a l 。也就是第 i i 块的最大值不能超过vali
还要对于每块预处理出一个集合 covi c o v i ,表示包含这一块的所有子矩形中,需求的最大值等于 vali v a l i 的子矩形集合。
DP转移:
1、填小于 vali v a l i 的数:

f[i+1][S]+=f[i][j]×(vali+11)areai+1 f [ i + 1 ] [ S ] + = f [ i ] [ j ] × ( v a l i + 1 − 1 ) a r e a i + 1

2、填 [1,vali] [ 1 , v a l i ] 的数,且至少一个 vali v a l i
f[i+1][S|covi+1]+=f[i][j]×(valareai+1i+1(vali+11)areai+1) f [ i + 1 ] [ S | c o v i + 1 ] + = f [ i ] [ j ] × ( v a l i + 1 a r e a i + 1 − ( v a l i + 1 − 1 ) a r e a i + 1 )

理论复杂度 O(T×4n) O ( T × 4 n ) ,但实际上远不达这个上界。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int E = 13, N = 1100, MX = 1e9 + 7;
int h, w, m, n, f[N][N], Cm, ara[N], cov[N], tot, sta[N];
int qpow(int a, int b) {
    int res = 1; while (b) b & 1 ? res = 1ll * res * a % MX : 0,
        a = 1ll * a * a % MX, b >>= 1; return res;
}
struct cyx {
    int X1, Y1, X2, Y2, V; cyx() {}
    cyx(int _, int __, int ___, int ____, int _____) :
        X1(_), Y1(__), X2(___), Y2(____), V(_____) {}
    int area() {return (X2 - X1 + 1) * (Y2 - Y1 + 1);}
} orz[E], uni[N];
cyx mers(cyx a, cyx b) {
    int l1 = a.X1, r1 = a.X2, l2 = b.X1, r2 = b.X2; cyx res;
    if (l1 > l2) swap(l1, l2), swap(r1, r2); if (r1 < l2) return
    cyx(0, 0, 0, 0, 0); res.X1 = l2; res.X2 = min(r1, r2);
    l1 = a.Y1; r1 = a.Y2; l2 = b.Y1; r2 = b.Y2;
    if (l1 > l2) swap(l1, l2), swap(r1, r2); if (r1 < l2) return
    cyx(0, 0, 0, 0, 0); res.Y1 = l2; res.Y2 = min(r1, r2);
    res.V = min(a.V, b.V); return res;
}
void dfs(int dep, cyx s, int st) {
    if (dep == n + 1) return (void) (uni[st] = s);
    dfs(dep + 1, s, st); cyx t = mers(s, orz[dep]);
    if (t.X1) dfs(dep + 1, t, st | (1 << dep - 1));
}
void work() {
    int i, j; h = read(); w = read(); m = read(); n = read();
    for (i = 1; i <= n; i++) orz[i].X1 = read(), orz[i].Y1 = read(),
        orz[i].X2 = read(), orz[i].Y2 = read(), orz[i].V = read();
    Cm = 1 << n; for (i = 0; i < Cm; i++) uni[i] = cyx(1, 1, 0, 0, m);
    dfs(1, cyx(1, 1, h, w, m), 0); for (i = 0; i < Cm; i++)
        ara[i] = uni[i].area(); for (i = Cm - 1; i; i--) {
        ara[0] -= ara[i]; for (j = (i - 1) & i; j; j = (j - 1) & i)
            ara[j] -= ara[i];
    }
    for (i = 0; i < Cm; i++) cov[i] = 0;
    for (i = 0; i < Cm; i++) for (j = 1; j <= n; j++)
        if (((i >> j - 1) & 1) && orz[j].V == uni[i].V)
            cov[i] |= 1 << j - 1; tot = 0;
    for (i = 1; i < Cm; i++) if (ara[i]) sta[++tot] = i;
    for (i = 0; i <= tot; i++) for (j = 0; j < Cm; j++) f[i][j] = 0; f[0][0] = 1;
    for (i = 0; i < tot; i++) {
        int S = sta[i + 1], xs = qpow(uni[S].V - 1, ara[S]),
            ys = (qpow(uni[S].V, ara[S]) - xs + MX) % MX;
        for (j = 0; j < Cm; j++) if (f[i][j])
            f[i + 1][j] = (f[i + 1][j] + 1ll * f[i][j] * xs % MX) % MX,
            f[i + 1][j | cov[S]] = (f[i + 1][j | cov[S]] + 1ll *
                f[i][j] * ys % MX) % MX;
    }
    printf("%d\n", 1ll * f[tot][Cm - 1] * qpow(m, ara[0]) % MX);
}
int main() {
    int T = read(); while (T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值