「解题报告」The 1st Universal Cup. Stage 2: Hong Kong

大概按难度排序。签到题和不会做的没写。

QOJ Codeforces Gym

B. Big Picture

给你一个 \((n + 1) \times (m + 1)\) 的方格矩阵,每个格子一开始是白色。

你会对每一行和每一列进行染色。将第 \(i\) 行的前 \(j\) 个格子染为黑色的概率为 \(p_{i, j}\),将第 \(j\) 列的前 \(i\) 个格子染为黑色的概率为 \(q_{i, j}\)。保证每一行的 \(p_{i, j}\)、每一列的 \(q_{i, j}\) 的和为 \(1\)。行与列是独立的,这意味着同一个格子可能会同时被行和列各染一次。

求同色四连通块的期望个数,模 \(998244353\)

\(n, m \le 10^4\)

简单的期望的线性性的应用。

首先容易发现所有黑色一定会连成同一个连通块,多出的一行一列也一定会连成一个连通块。考虑由黑色围出来的白色连通块。

由于黑色都是染一个前缀,那么每一个连通块的右下角一定是没有被染色的。我们考虑用连通块的右下角元素来代表整个连通块,根据期望的线性性,我们只需要考虑每个格子成为一个连通块的右下角的概率即可。

这个概率显然是当该格子没有被染色,且下方和右方都被染色的概率。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005, P = 998244353;
int n, m, p[MAXN][MAXN], q[MAXN][MAXN];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", &p[i][j]);
            p[i][j] = (p[i][j] + p[i][j - 1]) % P;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", &q[i][j]);
            q[i][j] = (q[i][j] + q[i - 1][j]) % P;
        }
    }
    int ans = 2;
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < m; j++) {
            ans = (ans + 1ll * p[i][j - 1] * q[i - 1][j] % P * (P + 1 - p[i + 1][j - 1]) % P
                * (P + 1 - q[i - 1][j + 1])) % P;
        }
    }
    printf("%d\n", ans);
    return 0;
}

C. Painting Grid

构造一个 \(n \times m\) 的 01 矩阵,满足:

  • 0 与 1 的出现次数相等;
  • 每一行的 01 序列不相等;
  • 每一列的 01 序列不相等。

\(n, m \le 1000\)

首先如果 \(n > 2^m\)\(m > 2^n\) 那么一定无解,若 \(n, m\) 均为奇数显然也一定无解,那么以下默认 \(n\) 为偶数。

那么我们考虑将整个矩形划分成上下两半,下半部分由上半部分翻转得来,那么出现次数的限制显然能够满足。那么我们现在得到了一个 \(\frac{n}{2} \times m\) 的网格,这里分两种情况:

  • \(m \ge 2^{\frac{n}{2}}\):那么发现,此时我们可以简单的每一列分别填入 \(0,1,2,3,\cdots2^\frac{n}{2} - 1\) 的二进制表示,循环填入。此时我们一定有不超过 \(2^\frac{n}{2}\)\([0, 2^{\frac{n}{2}})\) 的循环(因为有 \(m \le 2^n\)),且可以保证每一行均不相同。

    而由于上下两部分是翻转得来的,所以相当于下半部分也有不超过 \(2^\frac{n}{2}\)\([0, 2^{\frac{n}{2}})\) 的循环。而这 \(2^{\frac{n}{2}}\) 个数可以构成 \(2^n \ge m\) 个二元组,于是我们可以按照这样的二元组填入每一列,就能保证每一列互不相同了。

    具体构造就是将前几个整循环每次循环移位一个,剩下的散块不动即可。

    例如:

    \[\begin{aligned} \texttt{01230123012}\\ \texttt{12302301012} \end{aligned} \]

  • \(m < 2^{\frac{n}{2}}\):此时按照上面的构造方式可能出现两行均为 \(0\) 的情况,但是此时我们一定可以构造出一个矩形满足每行每列不相等。具体方法是先填入一个对角线,然后剩下的依次填入没有填入过的二进制数。可以证明,这样的构造一定能满足矩形与直接翻转后的矩形的每一行均不相等,证明大概就是对于 \(m\ge 3\) 对角线所在的行翻转后不相等(\(m=2\) 需要特判一下,因为对此时翻转后可能相等,实际上只有 \(n=4,m=2\) 需要特判),若 \(m \le \frac{n}{2}\) 则除对角线以外最高位一定都等于 \(0\),翻转后都等于 \(1\),所以也是互不相等的。

    例如:

    \[\begin{aligned} \texttt{100001010}\\ \texttt{010000110}\\ \texttt{001000001}\\ \texttt{000100000}\\ \end{aligned} \]

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int T, n, m;
int ans[MAXN][MAXN];
void solve(int n, int m) {
    int mid = (1 << (n / 2));
    if ((n / 2) <= 15 && m >= mid) {
        int w = (m - 1) / mid, v = m - w * mid;
        for (int i = 1; i <= w; i++) {
            for (int j = 0; j < mid; j++) {
                for (int k = 1; k <= (n / 2); k++) {
                    ans[k][(i - 1) * mid + j + 1] = j >> (k - 1) & 1;
                    ans[k + n / 2][(i - 1) * mid + j + 1] = !(((j + i) % mid) >> (k - 1) & 1);
                }
            }
        }
        for (int j = 0; j < v; j++) {
            for (int k = 1; k <= (n / 2); k++) {
                ans[k][w * mid + j + 1] = j >> (k - 1) & 1;
                ans[k + n / 2][w * mid + j + 1] = !(j >> (k - 1) & 1);
            }
        }
    } else {
        if (m <= n / 2) {
            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= m; j++) {
                    ans[i][j] = i == j;
                    ans[i + n / 2][j] = i != j;
                }
            }
            int val = 0;
            for (int i = m + 1; i <= n / 2; i++) {
                for (int j = 1; j <= m; j++) {
                    ans[i][j] = val >> (j - 1) & 1;
                    ans[i + n / 2][j] = !(val >> (j - 1) & 1);
                }
                val++;
                while (__builtin_popcount(val) == 1) val++;
            }
        } else {
            for (int i = 1; i <= n / 2; i++) {
                for (int j = 1; j <= n / 2; j++) {
                    ans[i][j] = i == j;
                    ans[i + n / 2][j] = i != j;
                }
            }
            int val = 0;
            for (int i = n / 2 + 1; i <= m; i++) {
                for (int j = 1; j <= n / 2; j++) {
                    ans[j][i] = val >> (j - 1) & 1;
                    ans[j + n / 2][i] = !(val >> (j - 1) & 1);
                }
                val++;
                while (__builtin_popcount(val) == 1) val++;
            }
        }
    }
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        if (1ll * n * m % 2 != 0 || (n <= 12 && m > (1 << n)) || (m <= 12 && n > (1 << m))) {
            printf("NO\n");
        } else {
            if (n == 4 && m == 2) {
                printf("YES\n00\n01\n10\n11\n");
            } else if (n % 2 == 0) {
                solve(n, m);
                printf("YES\n");
                for (int i = 1; i <= n; i++) {
                    for (int j = 1; j <= m; j++) {
                        printf("%d", ans[i][j]);
                    }
                    printf("\n");
                }
            } else {
                solve(m, n);
                printf("YES\n");
                for (int i = 1; i <= n; i++) {
                    for (int j = 1; j <= m; j++) {
                        printf("%d", ans[j][i]);
                    }
                    printf("\n");
                }
            }
        }
    }
    return 0;
}

D. Shortest Path Query

给定一张 DAG,每条边要不然是黑色要不然是白色,保证 \(1\) 号点可以到达所有点。

\(q\) 组询问 \((a, b, u)\),求若将所有白边边权设为 \(a\),黑边边权设为 \(b\)\(1\)\(u\) 的最短路。

\(n, q \le 50000, m \le 100000\)

DAG 上最短路是很经典的问题,拓扑排序 DP 即可。

但是本题的边权并不一定,不过我们仍然能够记录所有到 \(u\) 点的路径,将一条路径记为 \((x, y)\),表示经过了 \(x\) 条白边,\(y\) 条黑边,那么这条路径的真正长度就是 \(ax+by\)

容易发现,\(ax+by\) 的最小值一定取在一个凸包上的点,于是我们只需要维护 \((x, y)\) 的凸包即可。

可是凸包大小似乎还是 \(O(n)\) 的,看起来仍然无法通过。注意到这个凸包是一个整点凸包,且横纵坐标之和不超过 \(n\),于是可以分析出凸包上点数的最大值为 \(O(n^\frac{2}{3})\)

证明:考虑凸包是由若干条向量组成的,这些向量必定互不相等,设所有向量的 \((x_i, y_i)\) 满足 \(x_i, y_i \le m\),那么这样的向量最多有 \(O(m^2)\) 个,且向量横纵坐标总和为 \(O(m^3)\) 级别的,于是点数的数量级就是 \(O(n^\frac{2}{3})\) 的。

那么每次直接归并合并凸包,查询时凸包上二分,复杂度就是 \(O(m n^\frac{2}{3} + q \log n)\)

J. Dice Game

\(\frac{1}{n^2}\sum_{i=0}^{n - 1} \max(n \cdot i, \sum_{j=0}^{n - 1} (i \oplus j))\),对 \(998244353\) 取模。
\(T\) 组询问,\(T \le 10^4, n < 998244353\)

\[\begin{aligned} &\frac{1}{n^2}\sum_{i=0}^{n - 1} \max(n \cdot i, \sum_{j=0}^{n - 1} (i \oplus j))\\ =&\frac{1}{n^2}\sum_{i=0}^{n - 1} n \cdot i + \max(0, \sum_{j=0}^{n - 1} ((i \oplus j) - i))\\ =&\frac{1}{n^2}\sum_{i=0}^{n - 1} n \cdot i + \max(0, \sum_{j=0}^{29} (-1)^{d_{i,j}} c_j 2^j) \end{aligned} \]

其中 \(d_{i,j}\) 表示 \(i\) 的第 \(j\) 位,\(c_j\) 表示 \([0, n)\) 中第 \(j\) 位为 \(1\) 的数的个数。

\(v_i = c_i 2^i\),那么后面就是给 \(v_i\) 加若干正负号的和。发现 \(v_i\)\(v_{i - 1}\) 的差距应当是很大的,所以低位对正负性影响应当是较小的。打表可以发现,符号相同的段大致是有 \(O(\log n)\) 段,于是我们如果能找出这些段就可以将 \(\max\) 拆掉了。

考虑二分找每一段的端点,那么我们需要判断一段内符号是否相等,这可以通过数位 DP 找出区间内的最小值 / 最大值实现。同样求和也可以通过数位 DP 求出答案,总复杂度 \(O(T \log^3 n)\)

#include <bits/stdc++.h>
using namespace std;
const int P = 998244353;
int T;
int n;
long long v[31];
int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1ll * ans * a % P;
        a = 1ll * a * a % P;
        b >>= 1;
    }
    return ans;
}

long long f[32][2][2];
int g[32][2][2];
void chkmax(long long &a, long long b) { a = max(a, b); }
void chkmin(long long &a, long long b) { a = min(a, b); }
template<typename T>
void add(T &a, T b) { a = (a + b) % P; }
bool checkgz(int l, int r) {
    memset(f, 0x3f, sizeof f);
    f[30][1][1] = 0;
    for (int i = 29; i >= 0; i--) {
        for (int x = 0; x <= 1; x++) {
            for (int y = 0; y <= 1; y++) {
                int up = x ? (r >> i & 1) : 1;
                int down = y ? (l >> i & 1) : 0;
                for (int w = down; w <= up; w++) {
                    chkmin(f[i][x && w == up][y && w == down], f[i + 1][x][y] + v[i] * (w == 0 ? 1 : -1));
                }
            }
        }
    }
    return min({ f[0][0][0], f[0][0][1], f[0][1][0], f[0][1][1] }) >= 0;
}
bool checklz(int l, int r) {
    memset(f, 0xef, sizeof f);
    f[30][1][1] = 0;
    for (int i = 29; i >= 0; i--) {
        for (int x = 0; x <= 1; x++) {
            for (int y = 0; y <= 1; y++) {
                int up = x ? (r >> i & 1) : 1;
                int down = y ? (l >> i & 1) : 0;
                for (int w = down; w <= up; w++) {
                    chkmax(f[i][x && w == up][y && w == down], f[i + 1][x][y] + v[i] * (w == 0 ? 1 : -1));
                }
            }
        }
    }
    return max({ f[0][0][0], f[0][0][1], f[0][1][0], f[0][1][1] }) < 0;
}
int sum(int l, int r) {
    memset(f, 0, sizeof f);
    memset(g, 0, sizeof g);
    f[30][1][1] = 0;
    g[30][1][1] = 1;
    for (int i = 29; i >= 0; i--) {
        for (int x = 0; x <= 1; x++) {
            for (int y = 0; y <= 1; y++) {
                int up = x ? (r >> i & 1) : 1;
                int down = y ? (l >> i & 1) : 0;
                for (int w = down; w <= up; w++) {
                    add(f[i][x && w == up][y && w == down], f[i + 1][x][y]
                         + v[i] % P * (w == 0 ? 1 : P - 1) % P * g[i + 1][x][y]);
                    add(g[i][x && w == up][y && w == down], g[i + 1][x][y]);
                }
            }
        }
    }
    return (f[0][0][0] + f[0][0][1] + f[0][1][0] + f[0][1][1]) % P;
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 0; i <= 29; i++) {
            int c = (n >> (i + 1) << i) + max(0, n - (n >> (i + 1) << (i + 1)) - (1 << i));
            v[i] = (1ll * c) << i;
        }
        int ans = 1ll * n * n % P * (n - 1) % P * ((P + 1) / 2) % P;
        int l = 0;
        bool sgn = true;
        while (l != n) {
            int L = l, R = n - 1;
            while (L < R) {
                int mid = (L + R + 1) >> 1;
                if ((sgn ? checkgz : checklz)(l, mid)) L = mid;
                else R = mid - 1;
            }
            if (sgn) {
                ans = (ans + sum(l, L)) % P;
            }
            l = L + 1, sgn ^= 1;
        }
        int inv = qpow(n, P - 2);
        ans = 1ll * ans * inv % P * inv % P;
        printf("%d\n", ans);
    }
    return 0;
}

I. Range Closest Pair of Points Query

给定二维平面上 \(n\) 个点,\(q\) 次询问,查询所有编号在 \([l, r]\) 中欧几里得最小的点对。
\(n, q \le 250000, 1 \le x_i, y_i \le 10^8\)

P9062 [Ynoi2002] Adaptive Hsearch&Lsearch

考虑朴素平面最近点对的做法。平面最近点对的做法基本都基于“若当前点集中最近点对为 \(d\),那么每个 \(d \times d\) 的区域内的点数一定是 \(O(1)\) 的”。比如分治法计算跨中心点对时,只需要枚举 \(d \times d\) 的区域内的点对,非分治法每次加入一个点,同样只枚举 \(d \times d\) 的区域内的点对,随机增量法也是一样的做法,随机打乱后,每次按照 \(d \times d\) 划分成若干个网格,每次加入一个点只查询周围的几个格子,若 \(d\) 减小则重构。

注意到一个重点:上述算法中,将 \(d \times d\) 改成一个 \(kd \times kd\)\(k\) 为一个小常数),点数仍然是 \(O(1)\) 的。那么考虑用有限个网格来满足所有查询的需求,我们令 \(d\) 分别等于 \(2^0, 2^1, 2^2, \cdots, 2^{\lceil\log_2 V \rceil}\),进行 \(O(\log V)\) 次划分网格,这样就不需要随机化重构了。这个做法看起来更有前途,所以考虑拓展。

本题要求区间查询,而容易发现很多点对是没有用的,若 \(i_1 \le i_2 < j_2 \le j_1\)\(\mathrm{dis}(i_1, j_1) > \mathrm{dis}(i_2, j_2)\),那么 \((i_1, j_1)\) 一定是不可能作为答案的。那么我们可以考虑找出有用的支配对,可以猜测这样的支配对数是 \(O(n \log n)\) 的。

具体而言,我们假设最后的答案 \(d \in [2^{k - 1}, 2^k)\),那么发现,如果我们将点按照 \(2^k \times 2^k\) 划分网格,那么最后的点对要不然在相邻的两个格子内,要不然在同一个格子内。但是考虑这样的点对仍然会很多,但是我们注意到,同一个格子内两两距离均大于 \(2^{k - 1}\) 的点的个数是 \(O(1)\) 的,那么我们可以只考虑同一个格子内编号相差 \(O(1)\) 的点对,如果编号相差再多那么必定存在距离 \(< d\) 的点对,此时就一定不会造成贡献了。相邻格子同理。因此,一共能找到 \(O(n)\) 个支配对。对于每一个 \([2^{k - 1}, 2^k)\) 都找出这些支配对,就得到了 \(O(n \log n)\) 个支配对。

那么现在问题就变成了简单的区间内点对 \(\max\),注意到点对很多而查询较少,所以可以使用 \(O(1) - O(\sqrt{n})\) 的分块来平衡一下复杂度,这样总复杂度就是 \(O(n \log V + q \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 250005, B = 500;
int n, q;
int x[MAXN], y[MAXN];
vector<pair<int, int>> qs[MAXN];
long long dis(int i, int j) { return 1ll * (x[i] - x[j]) * (x[i] - x[j]) + 1ll * (y[i] - y[j]) * (y[i] - y[j]); }
long long ans[MAXN];
unordered_map<long long, pair<int, int>> mp;
int L[MAXN], R[MAXN], bl[MAXN];
long long val[MAXN], valb[MAXN];
void add(int d, long long v) { val[d] = min(val[d], v), valb[bl[d]] = min(valb[bl[d]], v); }
long long query(int l, int r) {
    long long ans = LLONG_MAX;
    for (int i = l; i <= R[bl[l]]; i++) ans = min(ans, val[i]);
    r = bl[r];
    for (int i = bl[l] + 1; i <= r; i++) ans = min(ans, valb[i]);
    return ans;
}
vector<int> pts[MAXN];
int p[MAXN];
int bx[MAXN], by[MAXN];
long long id(int x, int y) { return ((x + 5ll) << 30) | (y + 5ll); }
int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) scanf("%d%d", &x[i], &y[i]);
    for (int i = 1; i <= q; i++) {
        int l, r; scanf("%d%d", &l, &r);
        qs[r].push_back({ l, i });
    }
    for (int l = 1, r, i = 1; l <= n; l = r + 1, i++) {
        r = min(n, l + B - 1);
        L[i] = l, R[i] = r;
        for (int j = l; j <= r; j++) bl[j] = i;
    }
    auto ins = [&](int i, int s, int r) {
        for (int j = s; j <= s + 2 && j < r; j++) {
            pts[p[j]].push_back(p[i]);
        }
    };
    memset(val, 0x3f, sizeof val);
    memset(valb, 0x3f, sizeof valb);
    for (int k = 0; k <= 27; k++) {
        for (int i = 1; i <= n; i++) p[i] = i, bx[i] = x[i] >> k, by[i] = y[i] >> k;
        sort(p + 1, p + 1 + n, [&](int i, int j) {
            return bx[i] == bx[j] ? (by[i] == by[j] ? i < j : by[i] < by[j]) : bx[i] < bx[j];
        });
        for (int l = 1, r = 1; l <= n; l = r) {
            while (r <= n && bx[p[l]] == bx[p[r]] && by[p[l]] == by[p[r]]) r++;
            int X = bx[p[l]], Y = by[p[l]];
            mp[id(X, Y)] = { l, r };
            for (int i = l; i < r; i++) ins(i, i + 1, r);
        }
        for (int l = 1, r = 1; l <= n; l = r) {
            while (r <= n && bx[p[l]] == bx[p[r]] && by[p[l]] == by[p[r]]) r++;
            int X = bx[p[l]], Y = by[p[l]];
            for (auto [dx, dy] : vector<pair<int, int>>{ {-1, 1}, {0, 1}, {1, 1}, {1, 0} }) {
                if (!mp.count(id(X + dx, Y + dy))) continue;
                auto [pl, pr] = mp[id(X + dx, Y + dy)];
                int px = l, py = pl;
                while (px != r && py != pr) {
                    if (p[px] < p[py]) ins(px, py, pr), px++;
                    else ins(py, px, r), py++;
                }
            }
        }
        mp.clear();
    }
    for (int i = 1; i <= n; i++) {
        for (auto p : pts[i]) add(p, dis(p, i));
        for (auto p : qs[i]) ans[p.second] = query(p.first, i);
    }
    for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
    return 0;
} 
posted @ 2023-09-22 11:34  APJifengc  阅读(178)  评论(3编辑  收藏  举报