「解题报告」CF1290F Making Shapes

发布时间 2023-06-08 14:15:27作者: APJifengc

最近好像一直懒得写题解,但是感觉还是写一写比较好。

首先若干个向量组成一个凸包有经典做法,就是把向量按照极角排序,然后按照极角顺序依次拼接,得到的就是一个凸包,且方案唯一(由于本题限制不存在共线的两个向量)。

那么我们实际上只需要知道每个向量最终用了多少就可以了。设第 \(i\) 个向量用了 \(c_i\) 个,那么这个向量集合能拼成凸包当且仅当它们的和为 \(0\),也就是 \(\sum_{x_i > 0} c_i x_i = \sum_{x_i < 0} c_i (-x_i), \sum_{y_i > 0} c_i y_i = \sum_{y_i < 0} c_i (-y_i)\)

考虑高度的限制,由于凸包一定是先正后负,容易发现这就是在要求 \(\sum_{x_i > 0} c_i x_i \le m, \sum_{y_i > 0} c_i y_i \le m\)

那么我们要求的就是满足:

\[\sum_{x_i > 0} c_i x_i = \sum_{x_i < 0} c_i (-x_i), \sum_{y_i > 0} c_i y_i = \sum_{y_i < 0} c_i (-y_i), \sum_{x_i > 0} c_i x_i \le m, \sum_{y_i > 0} c_i y_i \le m \]

\(\{c_i\}\) 的数量。看起来不好做,但是注意到 \(x_i\) 很小,所以我们可以考虑数位 DP,按位确定 \(c_i\) 的值。这样这些限制很容易满足,直接做就行了。

#include <bits/stdc++.h>
using namespace std;
const int P = 998244353;
int n, m;
int x[5], y[5];
int g[18][18][18][18][2][2][30];
void add(int &a, int b) {
    a += b;
    if (a >= P) a -= P;
}
int f(int px, int nx, int py, int ny, bool xb, bool yb, int i) {
    if (i == 30) return px == 0 && nx == 0 && py == 0 && ny == 0 && xb && yb;
    int &cur = g[px][nx][py][ny][xb][yb][i];
    if (cur != -1) return cur;
    cur = 0;
    for (int s = 0; s < (1 << n); s++) {
        int npx = px, nnx = nx, npy = py, nny = ny;
        for (int j = 0; j < n; j++) if (s >> j & 1) {
            if (x[j] > 0) npx += x[j];
            if (x[j] < 0) nnx -= x[j];
            if (y[j] > 0) npy += y[j];
            if (y[j] < 0) nny -= y[j];
        }
        if ((npx & 1) != (nnx & 1) || (npy & 1) != (nny & 1)) continue;
        bool nxb = xb ? ((npx & 1) <= (m >> i & 1)) : ((npx & 1) < (m >> i & 1)),
             nyb = yb ? ((npy & 1) <= (m >> i & 1)) : ((npy & 1) < (m >> i & 1));
        add(cur, f(npx >> 1, nnx >> 1, npy >> 1, nny >> 1, nxb, nyb, i + 1));
    }
    return cur;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &x[i], &y[i]);
    }
    memset(g, -1, sizeof g);
    printf("%d\n", (f(0, 0, 0, 0, 1, 1, 0) - 1 + P) % P);
    return 0;
}