2025牛客暑期多校训练营2
A. Another Day of Sun
题意:一个\(01\)数组,有些地方是\(-1\)代表没填。数组的价值为所有最大全\(1\)区间的个数。最大全一区间表示这个区间不能再扩大了。求所有数组的价值和。
\(f[i][x][y]\)表示第\(i\)个填的\(x\)第\(i-1\)个填的\(y\)的价值和,\(cnt[i][x][y]\)表示其方案数。那么可以枚举\(z\)从\(f[i - 1][y][z]\)转移。如果\(x == 1 \&\& y == 0\),则再加上\(cnt[y][z]\),因为前面所有这个状态的数组价值都加一。
代码省略取模类。
点击查看代码
constexpr int P = 998244353;
using Z = MInt<P>;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::array<std::array<Z, 2>, 2> f{};
std::array<std::array<Z, 2>, 2> cnt{};
if (a[0] != -1 && a[1] != -1) {
f[a[1]][a[0]] = a[0] | a[1];
cnt[a[1]][a[0]] = 1;
} else if (a[0] != -1) {
f[0][a[0]] = a[0];
cnt[0][a[0]] = 1;
f[1][a[0]] = 1;
cnt[1][a[0]] = 1;
} else if (a[1] != -1) {
f[a[1]][0] = a[1];
cnt[a[1]][0] = 1;
f[a[1]][1] = 1;
cnt[a[1]][1] = 1;
} else {
for (int i = 0; i < 2; ++ i) {
for (int j = 0; j < 2; ++ j) {
f[i][j] = i | j;
cnt[i][j] += 1;
}
}
}
for (int i = 2; i < n; ++ i) {
std::array<std::array<Z, 2>, 2> g{};
std::array<std::array<Z, 2>, 2> ncnt{};
for (int j = 0; j < 2; ++ j) {
if (a[i] != -1 && j != a[i]) {
continue;
}
for (int k = 0; k < 2; ++ k) {
for (int x = 0; x < 2; ++ x) {
g[j][k] += f[k][x] + (j == 1 && k == 0) * cnt[k][x];
ncnt[j][k] += cnt[k][x];
}
}
}
f = g;
cnt = ncnt;
}
Z ans = 0;
for (int i = 0; i < 2; ++ i) {
for (int j = 0; j < 2; ++ j) {
ans += f[i][j];
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Bitwise Perfect
题意:求一个数组有没有两个数异或的值小于等于它们的最大值。
如果两个最高位相同的异或就一定满足条件。那么总共只有\(60\)位,所以\(61\)个人以上就一定有最高位相同的。直接暴力枚举就行了。
赛时没看出来,写了个字典树。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 5e5 + 5;
int Tr[N * 60][2], cnt[N * 60];
struct Trie {
int idx;
Trie() {
idx = 0;
Tr[0][0] = Tr[0][1] = 0;
cnt[0] = 0;
}
int new_node() {
++ idx;
Tr[idx][0] = Tr[idx][1] = 0;
cnt[idx] = 0;
return idx;
}
void insert(i64 x) {
int p = 0;
for (int i = 60; i >= 0; -- i) {
int s = x >> i & 1;
if (!Tr[p][s]) {
Tr[p][s] = new_node();
}
p = Tr[p][s];
++ cnt[p];
}
}
i64 query(i64 x) {
i64 res = 0;
int p = 0;
int flag = 1;
for (int i = 60; i >= 0; -- i) {
int s = x >> i & 1;
if (cnt[Tr[p][s]] > flag) {
p = Tr[p][s];
} else {
res += 1ll << i;
p = Tr[p][!s];
flag = 0;
}
}
return res;
}
};
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
Trie tr;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
tr.insert(a[i]);
}
for (int i = 0; i < n; ++ i) {
if (tr.query(a[i]) <= a[i]) {
std::cout << "NO\n";
return;
}
}
std::cout << "YES\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. Donkey Thinks...
题意:有\(n\)个物品和\(V\)的容量,每个物品有\(h_i, s_i, d_i\)。\(h_i, s_i\)分别是价值和体积。如果有\(v\)的空间是空的,那么每个放入背包的物品有\(-d_i \times v\)的贡献。求最大贡献。
考虑枚举放多少,这样就没有变量了。
但这样进行背包是\(nV\)的,在加上外层的枚举,显然无法通过。
考虑\(s_i\)种类较少,必定有大量体积相同的物品。那么这些物品按体积分类,考虑有\(m\)的容量,那么对于体积为\(s\)的物品,最多拿\(k = \lfloor \frac{m}{s} \rfloor\)个,我们肯定拿其中价值最大的。那么可以用\(nth\_element\)把前\(k\)大的放在最前面,然后枚举。那么总共枚举\(\sum_{i=1}^{m} \lfloor \frac{m}{s} \rfloor\),这是一个调和级数,是\(nlogn\)的,然后再加上背包的\(dp\),是\(mnlogn\),最后加上外层的枚举,都是和\(n\)同阶的,可以看成\(O(n^3logn)\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, V;
std::cin >> n >> V;
std::vector<int> h(n), s(n), d(n);
for (int i = 0; i < n; ++ i) {
std::cin >> h[i] >> s[i] >> d[i];
}
const i64 inf = 1e18;
i64 ans = 0;
for (int m = 1; m <= V; ++ m) {
std::vector<std::vector<i64>> g(m + 1);
for (int i = 0; i < n; ++ i) {
if (s[i] <= m) {
g[s[i]].push_back((i64)(V - m) * d[i] - h[i]);
}
}
std::vector<i64> f(m + 1, -inf);
f[0] = 0;
for (int i = 1; i <= m; ++ i) {
int k = std::min(m / i, (int)g[i].size());
std::nth_element(g[i].begin(), g[i].begin() + k - 1, g[i].end());
for (int j = 0; j < k; ++ j) {
for (int v = m; v >= i; -- v) {
f[v] = std::max(f[v], f[v - i] - g[i][j]);
}
}
}
ans = std::max(ans, f[m]);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
F. Field of Fire
题意:一个\(01\)环形数组,一开始从每个\(1\)开始往左右蔓延,总共蔓延\(t\)秒,你需要在一个\(0\)处建立防火带,使得不能蔓延过这个位置。求最多保留多少\(0\)。
不操作的话,对于一个长度为\(len\)的全\(0\)数组,剩下\(\max(0, len - 2t)\)个\(0\)。操作的话就剩下\(\max(0, len - t - 1)\)。显然\(len\)越大更可能保留下来。于是把所有全\(0\)数组拿出来,操作最长的,其它的不操作。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, t;
std::cin >> n >> t;
std::string s;
std::cin >> s;
std::vector<int> a;
for (int i = 0; i < n; ++ i) {
if (s[i] == '0') {
int j = i;
while (j + 1 < n && s[j + 1] == '0') {
++ j;
}
a.push_back(j - i + 1);
i = j;
}
}
if (a.empty()) {
std::cout << 0 << "\n";
return;
}
if (s[0] == '0' && s.back() == '0') {
a[0] += a.back();
a.pop_back();
}
std::ranges::sort(a);
int ans = 0;
for (int i = 0; i + 1 < a.size(); ++ i) {
ans += std::max(0, a[i] - 2 * t);
}
ans += std::max(0, a.back() - 1 - t);
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
G. Geometry Friend
题意:一个凸多边形围绕一个点旋转,求多少度后不能覆盖新的点。
如果点在凸多边形内,关注所有最远点与其相邻的最远点的最大角度就行了。
如果在凸多边形外,就是\(2pi\)。
具体实现中,除了计算角度其它都可以用\(longlong\)去算,计算角度最好用\(atan2\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
using T = i64;
const double PI = std::acos(-1);
struct Point {
T x, y;
Point(const T & _x = 0, const T & _y = 0) : x(_x), y(_y) {}
Point operator + (const Point & p) const {
return {x + p.x, y + p.y};
}
Point operator - (const Point & p) const {
return {x - p.x, y - p.y};
}
Point operator * (const T & v) const {
return {x * v, y * v};
}
Point operator / (const T & v) const {
return {x / v, y / v};
}
friend Point operator * (const T & v, const Point & p) {
return p * v;
}
Point & operator += (const Point & p) {
x += p.x;
y += p.y;
return *this;
}
Point & operator -= (const Point & p) {
x -= p.x;
y -= p.y;
return *this;
}
Point & operator *= (const T & v) {
x *= v;
y *= v;
return *this;
}
Point & operator /= (const T & v) {
x /= v;
y /= v;
return *this;
}
Point operator -() const {
return Point(-x, -y);
}
};
bool operator == (const Point & a, const Point & b) {
return a.x == b.x && a.y == b.y;
}
using Vector = Point;
//符号
int sign(const T & x) {
if (x == 0) {
return 0;
}
return x < 0 ? -1 : 1;
}
//点乘
T dot(const Point & a, const Point & b) {
return a.x * b.x + a.y * b.y;
}
//叉乘
T cross(const Point & a, const Point & b) {
return a.x * b.y - a.y * b.x;
}
//取模,模长,求长度
double Length(const Vector & a) {
return std::sqrt((double)dot(a, a));
}
//两向量夹角(弧度)
double Angle(const Vector & a, const Vector & b) {
return std::acos((double)dot(a, b) / Length(a) / Length(b));
}
//分类,极角排序用
int quad(const Point & a) {
if (a.y < 0) {
return 1;
}
if (a.y > 0) {
return 4;
}
if (a.x < 0) {
return 5;
}
if (a.x > 0) {
return 3;
}
return 2;
}
//极角排序
bool argcmp(const Point & a, const Point & b) {
int qa = quad(a), qb = quad(b);
if (qa != qb) {
return qa < qb;
}
auto t = cross(a, b);
//如果相同极角还需要按极径排序
// if (std::fabs(t) <= eps) {
// return dot(a, a) < dot(b, b) - eps;
// }
return t > 0;
}
bool cmp(const Point &A, const Point &B) {
bool upA = (A.y>0 || (A.y==0&&A.x>0));
bool upB = (B.y>0 || (B.y==0&&B.x>0));
if (upA != upB) return upA; // 上半平面先
return cross(A,B) > 0; // 同半平面,叉积>0 的角度更小
}
void solve() {
int n;
Point p;
std::cin >> n >> p.x >> p.y;
std::vector<Point> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i].x >> a[i].y;
}
for (int i = 0; i < n; ++ i) {
if (cross(a[(i + 1) % n] - a[i], p - a[i]) < 0) {
std::cout << PI * 2 << "\n";
return;
}
if (cross(a[(i + 1) % n] - a[i], p - a[i])==0 && dot(p-a[i], p-a[(i + 1) % n])>0) {
std::cout << PI * 2 << "\n";
return;
}
}
T maxd = 0;
for (int i = 0; i < n; ++ i) {
maxd = std::max(maxd, dot(p - a[i], p - a[i]));
}
std::vector<double> b;
for (int i = 0; i < n; ++ i) {
if (dot(p - a[i], p - a[i]) == maxd) {
auto t = p - a[i];
b.push_back(std::atan2(t.y, t.x));
}
}
std::ranges::sort(b);
double ans = 0;
int m = b.size();
if (m == 1) {
std::cout << PI * 2 << "\n";
return;
}
for (int i = 0; i + 1 < m; ++ i) {
ans = std::max(ans, b[i + 1] - b[i]);
}
ans = std::max(ans, b[0] + 2 * PI - b[m - 1]);
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cout << std::fixed << std::setprecision(12);
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
H. Highway Upgrade
题意:一个图。每个边有边权\(t_i\)和一个\(w_i\)。给你\(k\)你可以操作\(k\)次每次把一条边减去\(w_i\)。\(q\)次询问求\(1\)到\(n\)的最短路。保证\(i \in [1, m], t_i - w_i \times k > 0\)。
重要的是特殊条件:\(t_i - w_i \times k > 0\)。那么既然每条边都足够我们去减,在外面确定了路径的情况下,显然应该减\(w_i\)最大的边\(k\)次,也就是最优情况只操作一条边。
那么分别从\(1, n\)跑一遍单源最短路,对于一条边\(u, v, t, w\),如果操作\(k\)次,经过它的路径的最短长度为\(dist = d1[u]+ dn[v] + t - kw\),可以把它看作\(y = kx+b\)的形式。那么可以用李超线段树维护一次函数的最值。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct Line {
i64 m, b;
Line(i64 _m = 0, i64 _b = LLONG_MAX) : m(_m), b(_b) {}
i64 eval(i64 x) const { return m * x + b; }
};
struct Node {
Line ln;
Node *lch, *rch;
Node(const Line& _ln) : ln(_ln), lch(nullptr), rch(nullptr) {}
};
struct LiChao {
i64 L, R;
Node* root;
LiChao(i64 _L, i64 _R) : L(_L), R(_R), root(nullptr) {}
void add_line(i64 m, i64 b) {
insert(root, L, R, Line(m, b));
}
i64 query(i64 x) const {
return query_min(root, L, R, x);
}
void insert(Node*& nd, i64 l, i64 r, Line ln) {
if (!nd) {
nd = new Node(ln);
return;
}
i64 mid = (l + r) >> 1;
bool left = ln.eval(l) < nd->ln.eval(l);
bool mid_ = ln.eval(mid) < nd->ln.eval(mid);
if (mid_)
std::swap(ln, nd->ln);
if (l == r)
return;
if (left != mid_)
insert(nd->lch, l, mid, ln);
else
insert(nd->rch, mid + 1, r, ln);
}
i64 query_min(Node* nd, i64 l, i64 r, i64 x) const {
if (!nd) return LLONG_MAX;
i64 res = nd->ln.eval(x);
if (l == r) return res;
i64 mid = (l + r) >> 1;
if (x <= mid)
return std::min(res, query_min(nd->lch, l, mid, x));
else
return std::min(res, query_min(nd->rch, mid + 1, r, x));
}
};
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::tuple<int, int, i64, int>> edges(m);
std::vector<std::vector<std::pair<int, i64>>> adj(n);
std::vector<std::vector<std::pair<int, i64>>> radj(n);
for (int i = 0; i < m; ++ i) {
int u, v, w;
i64 t;
std::cin >> u >> v >> t >> w;
-- u, -- v;
adj[u].emplace_back(v, t);
radj[v].emplace_back(u, t);
edges[i] = {u, v, t, w};
}
const i64 inf = 1e18;
auto dijkstra = [&](std::vector<i64> & dist, std::vector<std::vector<std::pair<int, i64>>> & adj, int s) -> void {
using PII = std::pair<i64, int>;
std::priority_queue<PII, std::vector<PII>, std::greater<PII>> heap;
std::ranges::fill(dist, inf);
dist[s] = 0;
heap.emplace(dist[s], s);
while (heap.size()) {
auto [d, u] = heap.top(); heap.pop();
if (d != dist[u]) {
continue;
}
for (auto & [v, w] : adj[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
heap.emplace(dist[v], v);
}
}
}
};
std::vector<i64> d1(n), dn(n);
dijkstra(d1, adj, 0);
dijkstra(dn, radj, n - 1);
LiChao tr(1, 1e9);
for (auto & [u, v, t, w] : edges) {
i64 d = d1[u] + dn[v];
if (d < inf) {
tr.add_line(-w, d + t);
}
}
int q;
std::cin >> q;
while (q -- ) {
i64 k;
std::cin >> k;
std::cout << tr.query(k) << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
I. Identical Somehow
签到题。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int x, y;
std::cin >> x >> y;
if (x == 1 || y == 1) {
std::cout << -1 << "\n";
} else {
std::cout << 1 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
L. Love Wins All
题意:给你一个排列\(a\),\(i\)要么和\(a_i\)配对,要么和\(a_j = i\)的\(j\)配对。你只配对\(n-2\)个人,剩下两个人不配对。其有多少方案。
考虑\(i\)向\(a_i\)连边,那么两个人配对就是选择一条边。然后因为是排列,那么这个图每个点恰好有一个入度一个出度,显然构成了若干个环。
考虑一个环的人全部配对,奇数环显然不能全部配对,也就是说必须从不同的两个奇数环里选出两个人不配对,剩下的才可以配对,因为最多选两个人所以奇数环的数量只能是\(2\)或者\(0\)。如果是偶数环,那么每个点要么和顺时针方向配对,要么和逆时针配对,总共两种方案。
然后看怎么选出不匹配的点,如果有两个奇数环,显然方案数是两个奇数环点数相乘。如果没有奇数环,那么就是选一个偶数环,从中拿走两个,其方案数为点数一半的平方。
代码省略取模类。
点击查看代码
constexpr int P = 998244353;
using Z = MInt<P>;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::vector<int> cnt[2];
std::vector<int> vis(n + 1);
for (int i = 1; i <= n; ++ i) {
if (!vis[i]) {
int j = i;
int tot = 0;
while (!vis[j]) {
vis[j] = 1;
++ tot;
j = a[j];
}
cnt[tot & 1].push_back(tot);
}
}
if (cnt[1].size() > 2 || cnt[1].size() == 1) {
std::cout << 0 << "\n";
return;
}
if (cnt[1].size() == 2) {
Z ans = (Z)cnt[1][0] * cnt[1][1];
for (auto & x : cnt[0]) {
if (x == 2) {
ans *= 1;
} else {
ans *= 2;
}
}
std::cout << ans << "\n";
} else {
Z ans = 0;
Z sum = 1;
for (auto & x : cnt[0]) {
if (x == 2) {
sum *= 1;
} else {
sum *= 2;
}
}
for (auto & x : cnt[0]) {
if (x > 2) {
ans += sum * (x / 2) * (x / 2) / 2;
} else {
ans += sum * (x / 2) * (x / 2);
}
}
std::cout << ans << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}

浙公网安备 33010602011771号