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

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

QOJ Codeforces Gym

B. Big Picture

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

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

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

n,m104

点击查看题解

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

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

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

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

#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×m 的 01 矩阵,满足:

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

n,m1000

点击查看题解

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

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

  • m2n2:那么发现,此时我们可以简单的每一列分别填入 0,1,2,3,2n21 的二进制表示,循环填入。此时我们一定有不超过 2n2[0,2n2) 的循环(因为有 m2n),且可以保证每一行均不相同。

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

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

    例如:

    0123012301212302301012

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

    例如:

    100001010010000110001000001000100000

#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,黑边边权设为 b1u 的最短路。

n,q50000,m100000

点击查看题解

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

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

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

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

证明:考虑凸包是由若干条向量组成的,这些向量必定互不相等,设所有向量的 (xi,yi) 满足 xi,yim,那么这样的向量最多有 O(m2) 个,且向量横纵坐标总和为 O(m3) 级别的,于是点数的数量级就是 O(n23) 的。

那么每次直接归并合并凸包,查询时凸包上二分,复杂度就是 O(mn23+qlogn)

J. Dice Game

1n2i=0n1max(ni,j=0n1(ij)),对 998244353 取模。
T 组询问,T104,n<998244353

点击查看题解

1n2i=0n1max(ni,j=0n1(ij))=1n2i=0n1ni+max(0,j=0n1((ij)i))=1n2i=0n1ni+max(0,j=029(1)di,jcj2j)

其中 di,j 表示 i 的第 j 位,cj 表示 [0,n) 中第 j 位为 1 的数的个数。

vi=ci2i,那么后面就是给 vi 加若干正负号的和。发现 vivi1 的差距应当是很大的,所以低位对正负性影响应当是较小的。打表可以发现,符号相同的段大致是有 O(logn) 段,于是我们如果能找出这些段就可以将 max 拆掉了。

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

#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,q250000,1xi,yi108

点击查看题解

P9062 [Ynoi2002] Adaptive Hsearch&Lsearch

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

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

本题要求区间查询,而容易发现很多点对是没有用的,若 i1i2<j2j1dis(i1,j1)>dis(i2,j2),那么 (i1,j1) 一定是不可能作为答案的。那么我们可以考虑找出有用的支配对,可以猜测这样的支配对数是 O(nlogn) 的。

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

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

#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 @   APJifengc  阅读(191)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示