「解题报告」The 1st Universal Cup. Stage 2: Hong Kong
大概按难度排序。签到题和不会做的没写。
B. Big Picture
点击查看题解给你一个
的方格矩阵,每个格子一开始是白色。 你会对每一行和每一列进行染色。将第
行的前 个格子染为黑色的概率为 ,将第 列的前 个格子染为黑色的概率为 。保证每一行的 、每一列的 的和为 。行与列是独立的,这意味着同一个格子可能会同时被行和列各染一次。 求同色四连通块的期望个数,模
。
简单的期望的线性性的应用。
首先容易发现所有黑色一定会连成同一个连通块,多出的一行一列也一定会连成一个连通块。考虑由黑色围出来的白色连通块。
由于黑色都是染一个前缀,那么每一个连通块的右下角一定是没有被染色的。我们考虑用连通块的右下角元素来代表整个连通块,根据期望的线性性,我们只需要考虑每个格子成为一个连通块的右下角的概率即可。
这个概率显然是当该格子没有被染色,且下方和右方都被染色的概率。
#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
点击查看题解构造一个
的 01 矩阵,满足:
- 0 与 1 的出现次数相等;
- 每一行的 01 序列不相等;
- 每一列的 01 序列不相等。
首先如果
那么我们考虑将整个矩形划分成上下两半,下半部分由上半部分翻转得来,那么出现次数的限制显然能够满足。那么我们现在得到了一个
-
:那么发现,此时我们可以简单的每一列分别填入 的二进制表示,循环填入。此时我们一定有不超过 次 的循环(因为有 ),且可以保证每一行均不相同。而由于上下两部分是翻转得来的,所以相当于下半部分也有不超过
次 的循环。而这 个数可以构成 个二元组,于是我们可以按照这样的二元组填入每一列,就能保证每一列互不相同了。具体构造就是将前几个整循环每次循环移位一个,剩下的散块不动即可。
例如:
-
:此时按照上面的构造方式可能出现两行均为 的情况,但是此时我们一定可以构造出一个矩形满足每行每列不相等。具体方法是先填入一个对角线,然后剩下的依次填入没有填入过的二进制数。可以证明,这样的构造一定能满足矩形与直接翻转后的矩形的每一行均不相等,证明大概就是对于 对角线所在的行翻转后不相等( 需要特判一下,因为对此时翻转后可能相等,实际上只有 需要特判),若 则除对角线以外最高位一定都等于 ,翻转后都等于 ,所以也是互不相等的。例如:
#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,每条边要不然是黑色要不然是白色,保证
号点可以到达所有点。 有
组询问 ,求若将所有白边边权设为 ,黑边边权设为 , 到 的最短路。
DAG 上最短路是很经典的问题,拓扑排序 DP 即可。
但是本题的边权并不一定,不过我们仍然能够记录所有到
容易发现,
可是凸包大小似乎还是
证明:考虑凸包是由若干条向量组成的,这些向量必定互不相等,设所有向量的
那么每次直接归并合并凸包,查询时凸包上二分,复杂度就是
J. Dice Game
点击查看题解求
,对 取模。
组询问, 。
其中
设
考虑二分找每一段的端点,那么我们需要判断一段内符号是否相等,这可以通过数位 DP 找出区间内的最小值 / 最大值实现。同样求和也可以通过数位 DP 求出答案,总复杂度
#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
点击查看题解给定二维平面上
个点, 次询问,查询所有编号在 中欧几里得最小的点对。
P9062 [Ynoi2002] Adaptive Hsearch&Lsearch。
考虑朴素平面最近点对的做法。平面最近点对的做法基本都基于“若当前点集中最近点对为
注意到一个重点:上述算法中,将
本题要求区间查询,而容易发现很多点对是没有用的,若
具体而言,我们假设最后的答案
那么现在问题就变成了简单的区间内点对
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本