2025.1.3 CW 模拟赛

题面 & 题解

T1

算法

贪心, 数学, 高精度.

思路

根据贪心, 对于两个数 \(A, B\), 我们一定会从高位到低位从大到小地填数.
考虑填法, 设 \(A\) 的前缀为 \(a\), \(B\) 的前缀为 \(b\), 将要填入的数为 \(c\).

如果将其填入 \(A\), 那么答案为 \((a \times 10 + c) \times b\).
如果将其填入 \(B\), 那么答案为 \((b \times 10 + c) \times a\).
两式相减, 得: \(c \times (b - a)\).

所以一定会将 \(c\) 放在较小的前缀后.

考虑 \(a = b\) 的情况.
先说结论, 放在剩余位数较小的数上.
以下是证明:

钦定 \(A \le B\).
对于 \(a = b\) 之前的情况, 填数必然是交替进行的, 也就是对于 \(a, b\) 的每一位, 两个填入的数是确定的.
那么 \(a + b\) 即为定值, 显然地, 和定差小积大.
而对于 \(a\), 我们可以在其尾部补 0 直至 \(A = B\), 这对答案是没有任何影响的.
根据上面的推论, 为了使两数之差更小, 将 \(c\) 填入 \(a\) 一定是不劣的.

最后因为 \(A, B \le 10^3\), 需要高精度乘.

#include "iostream"
#include "algorithm"
#include "cstring"

using namespace std;

constexpr int N = 1e3 + 10;

int a, b;
basic_string<int> t;

void init() {
	t.clear();
	scanf("%d %d", &a, &b);
	if (a > b)
		swap(a, b);
	for (int i = 1, x; i <= 9; ++i) {
		scanf("%d", &x);
		for (int j = 1; j <= x; ++j)
			t.push_back(i);
	}
	reverse(t.begin(), t.end());
}

int x[N], y[N], cx, cy;
int ans[N << 1];

void calculate() {
	cx = cy = 0;
	memset(ans, 0, sizeof ans);
	for (int i = 0; i < (a << 1); i += 2)
		x[++cx] = t[i], y[++cy] = t[i + 1];
	for (int i = a << 1; i < (a + b); ++i)
		y[++cy] = t[i];
	for (int i = 1, f = 0; i <= a; ++i) if (x[i] ^ y[i]) {
		if (x[i] > y[i] and !f) {
			f = 1;
			continue;
		}
		if (f and x[i] > y[i])
			swap(x[i], y[i]);
	}
	reverse(x + 1, x + a + 1), reverse(y + 1, y + b + 1);
	for (int i = 1; i <= b; ++i) {
		for (int j = 1, tmp, nxt = 0; j <= a; ++j) {
			int pos = j + i - 1;
			tmp = y[i] * x[j];
			ans[pos] += tmp % 10 + nxt, nxt = tmp / 10 + ans[pos] / 10;
			ans[pos] %= 10;
			if (j == a and nxt) {
				ans[++pos] = nxt;
				break;
			}
		}
	}
	for (int i = a + b, lead = 1; i; --i) {
		if (lead and !ans[i])
			continue;
		if (ans[i] or !lead) 
			putchar(ans[i] + '0'), lead = 0;
	}
	putchar('\n');
}

void solve() {
	init();
	calculate();
}

int main() {
	int t;
	scanf("%d", &t);
	while (t--)
		solve();
	return 0;
}

T2

算法

数据结构, 博弈论.

思路

首先可以发现 Alice 所选的区间长度为 \(\lceil \frac{n}{2} \rceil\).

假设 Alice 第一步选择了 \(i\), 又因为 ta 只能够选择一个相邻的未被占用的点, 那么最后选择的一定是一段连续的, 并且包含 \(i\) 的区间.

此时对于 Bob, 他一定有一种方法使得 Alice 只能选择和最小的一段区间.

那么问题就转化为了枚举每一个 \(i \in [1, n]\), 找到包含位置 \(i\) 的长度为 \(\lceil \frac{n}{2} \rceil\) 的一段连续区间的最小值, 最后对于所有的最小值取 \(\max\) 即可.

实现上注意边界即可, 采用单调队列做到 \(\mathcal{O} (n)\).

#include "iostream"
#include "deque"

using namespace std;

constexpr int N = 5e5 + 10;

#define int long long

int n;
int v[N], sum[N << 1];
deque<int> q;

void init() {
	cin >> n;
	for (int i = 1; i <= n; ++i)
		cin >> v[i];
	for (int i = 1; i <= 2 * n; ++i) {
		if (i > n) sum[i] = sum[i - 1] + v[i - n];
		else sum[i] = sum[i - 1] + v[i];
	}
}

void calculate() {
	int len = (n - 1) / 2 + 1, ans = -1;
	for (int i = 1; i + len - 1 <= n << 1; ++i) {
		while (!q.empty() and q.front() + len - 1 < i) 
			q.pop_front();
		while (!q.empty() and sum[i + len - 1] - sum[i - 1] <= sum[q.back() + len - 1] - sum[q.back() - 1]) 
			q.pop_back();
		q.push_back(i);
		if (i >= len) 
			ans = max(ans, sum[q.front() + len - 1] - sum[q.front() - 1]);
	}
	cout << ans << '\n';
}

void solve() {
	init();
	calculate();
}

signed main() {
	cin.tie(nullptr)->sync_with_stdio(false);
	solve();
	return 0;
}

T4

算法

分块, 树状数组.

思路

直接暴力是 \(\mathcal{O} (qnm)\) 的, 无法通过.

考虑对于环分别操作, 我们定义长度 \(> B\) 的环为大环, \(\le B\) 的为小环.
具体地, 对于大环, 维护环上前缀和, 我们在修改的时候直接打标记, 这样在查询的时候是 \(\mathcal{O}(\frac{n}{B} \log n)\) 的.
对于小环, 考虑用树状数组维护, 修改 \(\mathcal{O}(B \log n)\), 查询 \(\mathcal{O}(\log n)\).

\(B\)\(\sqrt{n \log n}\) 是最优, 总时间复杂度 \(\mathcal{O}(n \sqrt{n \log n})\). (有点卡常)

#include "iostream"
#include "algorithm"

using namespace std;

#define int long long
#define lowbit(x) x & -x

template <class T>
void read(T &x) {
	x = 0;
	char ch = getchar();
	while (ch < '0' or ch > '9')
		ch = getchar();
	while (ch >= '0' and ch <= '9')
		x = x * 10 + ch - 48, ch = getchar();
}

void print(int x) {
	if (x > 9)
		print(x / 10);
	putchar(x % 10 + '0');
}

constexpr int N = 1.5e5 + 10, B = 666;

int n, m, Q;
int a[N], tg[N];
basic_string<int> c[N], pre[N], ex;

class Binary_Indexed_Tree {
protected:
	int tr[N];
	
public:	
	void update(int x, int k) { for (; x <= n; x += lowbit(x)) tr[x] += k; }
	
	int query(int x) {
		int ret = 0;
		for (; x; x -= lowbit(x))
			ret += tr[x];
		return ret;	
	}
	
	int query(int l, int r) { return query(r) - query(l - 1); }
	
} bit;

void init() {
	read(n), read(m), read(Q);
	for (int i = 1, x; i <= n; ++i)
		read(x), c[x].push_back(i);
	for (int i = 1; i <= n; ++i)
		read(a[i]);
	for (int i = 1; i <= m; ++i) {
		if (c[i].size() <= B) {
			for (int x : c[i])
				bit.update(x, a[x]);
		}
		else {
			ex.push_back(i);
			int tmp = 0;
			for (int x : c[i])
				pre[i].push_back(tmp += a[x]);
		}
	}
}

int deal(int x, int l, int r) {
	l = lower_bound(c[x].begin(), c[x].end(), l) - c[x].begin(),
	r = upper_bound(c[x].begin(), c[x].end(), r) - c[x].begin() - 1;
	if (l >= (int)c[x].size() or r < l)
		return 0;
	l = (l + c[x].size() - tg[x]) % c[x].size(), r = (r + c[x].size() - tg[x]) % c[x].size();
	if (l > r)
		return pre[x][pre[x].size() - 1] - (pre[x][l - 1] - pre[x][r]);
	return pre[x][r] - (l ? pre[x][l - 1] : 0);
}

void swp(int x) {
	if (c[x].empty())
		return;
	for (int i : c[x])
		bit.update(i, -a[i]);
	bit.update(c[x][0], a[c[x][c[x].size() - 1]]);
	for (int i = c[x].size() - 1; i; --i)
		bit.update(c[x][i], a[c[x][i - 1]]);
	for (int i = c[x].size() - 1; i; --i)
		swap(a[c[x][i]], a[c[x][i - 1]]);
}

void calculate() {
	while (Q--) {
		int op, l, r;
		read(op), read(l);
		if (op & 1) {
			read(r);
			int ans = bit.query(l, r);
			for (int x : ex)
				ans += deal(x, l, r);
			print(ans), putchar('\n');
		}
		else {
			if (c[l].size() <= B)
				swp(l);
			else
				++tg[l];
		}
	}
}

void solve() {
	init();
	calculate();
}

signed main() {
	solve();
	return 0;
}
posted @ 2025-01-03 19:24  Steven1013  阅读(19)  评论(0)    收藏  举报