[BZOJ5299][Cqoi2018]解锁屏幕(状压dp)

Description

使用过 Android 手机的同学一定对手势解锁屏幕不陌生。 Android 的解锁屏幕由 3×3 3 × 3 个点组成,手指在屏幕上画一条
线将其中一些点连接起来,即可构成一个解锁图案。如下面三个例子所示:
这里写图片描述
画线时还需要遵循一些规则
1.连接的点数不能少于 4 4 个。也就是说只连接两个点或者三个点会提示错误。
2.两个点之间的连线不能弯曲。
3.每个点只能“使用”一次,不可重复。这里的“使用”是指手指划过一个点,该点变绿。
4.两个点之间的连线不能“跨过”另一个点,除非那个点之前已经被“使用”过了。
对于最后一条规则,参见下图的解释。左边两幅图违反了该规则:而右边两幅图
(分别为 24136 54192 5 → 4 → 1 → 9 → 2
则没有违反规则,因为在“跨过”点时,点已经被“使用”过了。
这里写图片描述
现在工程师希望改进解锁屏幕,增减点的数目,并移动点的位置,不再是一个九宫格形状,但保持上述画线的规则不变。
请计算新的解锁屏幕上,一共有多少满足规则的画线方案。

Input

输入文件第一行,为一个整数 n n ,表示点的数目。
接下来 n 行,每行两个空格分开的整数 xi x i yi y i ,表示每个点的坐标。
1000xi,yi1000 − 1000 ≤ x i , y i ≤ 1000 1n<20 1 ≤ n < 20 。各点坐标不相同。

Output

输出文件共一行,为题目所求方案数除以 100000007 100000007 的余数。

Sample Input

4
0 0
1 1
2 2
3 3

Sample Output

8

样例解释

4 4 个点编号为 1 4 4 ,方案有
1234,2134,3214,2314
及其镜像
4321,3421,2341,3241 4 → 3 → 2 → 1 , 3 → 4 → 2 → 1 , 2 → 3 → 4 → 1 , 3 → 2 → 4 → 1

Solution

先预处理出对于每两个点 (i,j) ( i , j ) ,连成的边会经过哪些点(不包括 i i j ),压缩成一个二进制数表示集合,记为 set[i,j] s e t [ i , j ]
然后显然状压dp:
f[S][i] f [ S ] [ i ] 表示经过的点集为 S S ,最后一个到达的点为 i 的方案数。
边界: |S|=1 | S | = 1 时,如果 S={i} S = { i } ,那么 f[S][i]=1 f [ S ] [ i ] = 1 ,否则 f[S][i]=0 f [ S ] [ i ] = 0
转移:枚举 jS j ∈ S ji j ≠ i set[i,j]S s e t [ i , j ] ⊂ S

f[S][i]+=f[S{i}][j] f [ S ] [ i ] + = f [ S − { i } ] [ j ]

最后答案:
|S|>3iSf[S][i] ∑ | S | > 3 ∑ i ∈ S f [ S ] [ i ]

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
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 N = 23, C = (1 << 20) + 5, PYZ = 1e8 + 7;
int n, Cm, X[N], Y[N], to[N][N], f[C][N], cnt[C], ans;
bool check(int x1, int y1, int x2, int y2, int x3, int y3) {
    int x4 = x1 - x2, y4 = y1 - y2, x5 = x3 - x2, y5 = y3 - y2;
    if (x1 > x3) swap(x1, x3); if (y1 > y3) swap(y1, y3);
    return x4 * y5 == x5 * y4 && x1 <= x2 && x2 <= x3 && y1 <= y2 && y2 <= y3;
}
int main() {
    int i, j, k; n = read(); For (i, 1, n) X[i] = read(), Y[i] = read();
    For (i, 1, n) For (j, 1, n) if (i != j) For (k, 1, n)
        if (k != i && k != j && check(X[i], Y[i], X[k], Y[k], X[j], Y[j]))
            to[i][j] |= 1 << k - 1; Cm = (1 << n) - 1;
    For (i, 1, Cm) {
        For (j, 1, n) if ((i >> j - 1) & 1) cnt[i]++; For (j, 1, n) {
            if (!((i >> j - 1) & 1)) continue;
            if (cnt[i] == 1) {f[i][j] = 1; continue;}
            For (k, 1, n) if (((i >> k - 1) & 1) && j != k
                && (to[j][k] & i) == to[j][k])
                    f[i][j] = (f[i][j] + f[i ^ (1 << j - 1)][k]) % PYZ;
            if (cnt[i] > 3) ans = (ans + f[i][j]) % PYZ;
        }
    }
    cout << ans << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值