A - Balloon Trip
题意
每个气球可以撑起 \(B\) 克的重量。如果往一个物体上挂 \(n\) 个气球,只有当物体重量严格小于 \(nB\) 克时,这个物体才会升上天空。
高桥的体重是 \(W\) 千克。问至少需要在高桥身上挂多少个气球,才能让高桥升上天空?
思路
问一个最小的正整数 \(n\) 满足:
代码
void solve()
{int w, b;cin >> w >> b;cout << 1000 * w / b + 1;
}
B - Bird Watching
题意
天空中有 \(N\) 只鸟在飞,分别编号为 \(1, 2, \dots, N\)。
鸟的种类共有 \(M\) 种,分别编号为 \(1, 2, \dots, M\)。
第 \(i\) 只鸟的种类为 \(A_i\),大小为 \(B_i\)。
对于 \(k = 1, 2, \dots, M\),请求出第 \(k\) 种鸟的平均大小。
思路
借助计数数组统计每一种鸟的大小总和以及数量,最后计算输出即可。
代码
int sum[105], cnt[105];void solve()
{int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){int a, b;cin >> a >> b;sum[a] += b;cnt[a]++;}for(int i = 1; i <= m; i++)printf("%.20f\n", 1.0 * sum[i] / cnt[i]);
}
C - Flapping Takahashi
题意
高桥决定通过气球飞上天空。
在第 \(0\) 秒,高桥的高度为 \(H\),并且他打算从现在开始飞行 \(10^9\) 秒。
高桥每秒最多可以改变 \(1\) 个单位的飞行高度。但过程中飞行高度必须保持大于 \(0\) 。
对于飞行高度,高桥有 \(N\) 个目标,第 \(i\) 个目标是指他的高度在第 \(t_i\) 秒时必须在区间 \([l_i, u_i]\) 范围内。
高桥是否能够实现所有的目标?
思路
在过程中我们可以维护一个区间 \([a, b]\),表示在每个目标相关的时刻,我们能够移动到的高度范围。
一开始 \(t = 0\) 时,区间为 \([H, H]\)。
接下来每输入一个目标信息 \(t_i, l_i, u_i\),先处理当前的时间 \(t_i\) 与上一个目标的时间 \(t_{i-1}\) 之间的差值(没有上一个目标则为 \(0\)),两者时间差 \(t_i - t_{i-1}\) 可以用于扩大我们可到达的高度范围。
假设时间为 \(t_{i-1}\) 时可到达的高度区间为 \([a, b]\),则当时间为 \(t_i\) 时,高度最小值可以下降 \(t_i - t_{i-1}\) 个单位,但由于高度不能降至 \(0\) 及以下,则区间左端点可以修正为 \(\max(1, a - (t_i - t_{i-1}))\),同理右端点可以修正为 \(b + (t_i - t_{i-1})\)。这便是在不考虑第 \(i\) 个目标的前提下,我们在时间 \(t_i\) 所能到达的高度范围。
考虑第 \(i\) 个目标,由于该目标限制我们在时间 \(t_i\) 的高度必须在 \([l_i, u_i]\) 范围内,因此只需要两区间取交集即可,即两个左端点取较大值,两个右端点取较小值。
最后,如果某个目标无法实现,则区间内不包含任何合法数值,此时 \(a \gt b\) 成立。
单组数据时间复杂度 \(O(N)\)。
代码
void solve()
{int n, h;cin >> n >> h;long long a = h, b = h, last = 0;// 在 last 时间能够到达的高度区间 [a, b]bool flag = true;// 标记过程中是否有某个目标无法实现for(int i = 1; i <= n; i++){long long t, l, u;cin >> t >> l >> u;a = max(1LL, a - (t - last));b = b + (t - last);last = t;a = max(a, l);b = min(b, u);if(a > b)flag = false;}if(flag == false)cout << "No\n";elsecout << "Yes\n";
}
signed main()
{int T;cin >> T;while(T--){solve();}return 0;
}
D - Clouds
题意
天空由一个 \(2000 \times 2000\) 的网格表示。当仰望天空时,我们将第 \(r\) 行第 \(c\) 列的单元格称作 \((r,c)\) 。
目前,天空中漂浮着 \(N\) 朵云,分别编号为 \(1,2,\dots,N\)。
当且仅当单元格 \((r,c)\) 满足 \(U_i \le r \le D_i\) 和 \(L_i \le c \le R_i\) 时,它才会被第 \(i\) 朵云所覆盖。
对于每个 \(k=1,2,\dots,N\),回答以下问题:
- 如果从这 \(N\) 朵云中删除第 \(k\) 朵云,问此时有多少个单元格没有被任何一朵云覆盖?
思路
先借助二维差分数组将每朵云表示的子矩阵内每个单元格全部 \(+1\),再通过二维前缀和维护每个单元格分别被多少朵云覆盖。
如果某个单元格没有被任何云覆盖,则该位置恒为答案,用一个变量 sum 记录恒为答案的位置数量。
而如果某个单元格只被一朵云覆盖,那之后只需要在处理这朵云的询问时,将当前单元格加入到这朵云的答案里即可。但现在我们并不知道覆盖当前单元格的这朵云编号是什么,但这无关紧要,我们只需要再来一个二维数组 b[][],用于维护有哪些单元格只被一朵云覆盖。如果 \((i, j)\) 只被一朵云覆盖,则 b[i][j] 记作 \(1\),否则记作 \(0\)。
等到所有位置全部统计完成后,对 b[][] 数组再做一次二维前缀和,以便于我们接下来针对每朵云所覆盖的子矩阵,快速计算子矩阵内部有多少个单元格只被一朵云覆盖,最后加上 sum 后输出即可。
时间复杂度 \(O(N + M^2)\),\(M = 2000\)。
代码
int n;
int u[200005], d[200005], l[200005], r[200005];
int a[2005][2005], b[2005][2005];
// a[i][j] 表示 (i, j) 被多少朵云覆盖
// b[i][j] 初始表示 (i, j) 是否只被一朵云覆盖void solve()
{cin >> n;for(int i = 1; i <= n; i++){cin >> u[i] >> d[i] >> l[i] >> r[i];// 子矩阵全部 +1a[u[i]][l[i]]++;a[u[i]][r[i] + 1]--;a[d[i] + 1][l[i]]--;a[d[i] + 1][r[i] + 1]++;}int sum = 0; // 有多少位置一朵云也没有for(int i = 1; i <= 2000; i++)for(int j = 1; j <= 2000; j++){a[i][j] = a[i][j]+ a[i - 1][j]+ a[i][j - 1]- a[i - 1][j - 1];if(a[i][j] == 1)b[i][j]++; // (i, j) 只被一朵云覆盖,标记该位置else if(a[i][j] == 0)sum++;}for(int i = 1; i <= 2000; i++)for(int j = 1; j <= 2000; j++)b[i][j] = b[i][j]+ b[i - 1][j]+ b[i][j - 1]- b[i - 1][j - 1];for(int i = 1; i <= n; i++){int cnt = b[d[i]][r[i]]- b[d[i]][l[i] - 1]- b[u[i] - 1][r[i]]+ b[u[i] - 1][l[i] - 1]; // 子矩阵内部只被一朵云覆盖的网格数量cout << sum + cnt << "\n";}
}
E - Distribute Bunnies
题意
在一条数轴上有 \(N\) 只兔子,编号为 \(1\) 至 \(N\) 。第 \(i\) 只兔子位于坐标 \(X_i\),其跳跃力为 \(R_i\)。同一坐标上可能有多只兔子。
现在所有兔子都会跳跃一次。对于一只位于坐标 \(x\) 且跳跃能力为 \(r\) 的兔子而言,跳跃一次后它会移动到坐标 \(x+r\) 或坐标 \(x-r\) 处。
如果我们可以自由地选择让每只兔子跳到哪个坐标,请找出在所有兔子跳完后,最多能有多少个不同的坐标存在兔子。
思路一
对于一只位于坐标 \(x\) 且跳跃能力为 \(r\) 的兔子,可以在 \(x - r\) 与 \(x + r\) 两个数之间选择一个数字当作它的最终位置。
可以尝试将 \(x - r\) 与 \(x + r\) 当作图中的两点,并在这两点间进行连边,那么问题便变成了“对于图中的每条边,都有一次机会可以选择其两端点中的任意一点进行染色,问最多可以对多少个点进行染色”。
考虑图中的每一个连通块对答案的贡献。因为图中环的点数与边数相同,进一步思考发现只要连通块中存在至少一个环,我们就有办法为环上的每条边都分配一个点进行染色,所有点便都可以被染上色,然后再从这个环出发,每多一条连向这个环的边,就将下一个点分配给这条边,因此这种情况下连通块上的所有点均能够被染色。
反之,另一种极端情况的连通块就是树了,因为树上不存在任何环。对于一棵包含 \(n\) 个点的树而言,其只有 \(n-1\) 条边,因此最多只能够对 \(n-1\) 个点进行染色,剩余的一个点无法被染色。
也就是说,这张图中有多少棵树,就会有多少个点无法被染色,换到原题意内,就说明有多少个可能出现的位置是无法被兔子占据的。
所以只需要先借助 set 容器,对于每只兔子把所有可能安排的位置(\(x-r\) 与 \(x+r\))全部存起来,去重后得出总共有多少个可以安排兔子的点。然后按照上述步骤建图后,判断有多少个连通块是一棵树,就减去等数量的点后输出即可。
判断树的方法有多种,可以深搜判环,或者判断边数与点数关系等。至于存图,借助 map 嵌套 vector 建立邻接表即可,或者对所有可能出现的坐标离散化后再建立邻接表。
以下代码采用 map 嵌套 vector 建立邻接表,以深搜判环的方法实现,时间复杂度 \(O(N\log^2 N)\)。
代码一
int n;
set<int> st, vis;
// st 存储所有可能的位置
// vis 标记每个位置是否已被访问过
map<int, vector<int>> G;
// 邻接表 G[x] 存储与位置 x 相连的边的信息bool dfs(int u, int fa)
{vis.insert(u);for(int &v : G[u]){if(v == fa)continue;if(vis.count(v) == 1)return true; // 有环if(dfs(v, u))return true;}return false;
}void solve()
{cin >> n;for(int i = 1; i <= n; i++){int x, r;cin >> x >> r;st.insert(x - r);st.insert(x + r);G[x - r].push_back(x + r);G[x + r].push_back(x - r);}int ans = st.size();for(int i : st){if(vis.count(i) == 0){if(!dfs(i, 2147483647))ans--; // 如果 i 点所在连通块是一棵树,可匹配位置 -1}}cout << ans;
}
思路二
可通过“兔子 - 可行位置”建立二分图,每只兔子只会对应两个可行位置,因此边数与点数量级相同。
然后直接跑最大流的 Dinic 模板即可,因为是二分图且图为单位网络,故时间复杂度 \(O(N\sqrt N)\)。