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;
}
posted @ 2025-07-18 09:38  maburb  阅读(384)  评论(0)    收藏  举报