2025.1.7 CW 模拟赛

题面 & 题解

T1

做题做少了导致的.

算法

并查集, 组合数学.

思路

首先考虑什么情况下答案一定是 0: 若对于第 \(i\) 列和第 \(n - i - 1\) 列的和 \(> 2\), 那么就一定无解.

在分类讨论下:

  1. \(i\) 列和第 \(n - i - 1\) 列的和 \(\le 1\), 那该行可翻转可不翻转.

  2. \(i\) 列和第 \(n - i - 1\) 列的和 \(= 2\), 如果这两个 \(1\) 在同一列.

    若两个 \(1\) 在同一列, 那么必须一列翻转, 一列不翻转.

    若两个 \(1\) 不在同一列, 那么必须同时翻转或者不翻转.

考虑怎么实现.

互斥并查集, 定义 \(i\) 为翻转这一行, \(i + n\) 为不翻转这一行, \(i_1, i_2\) 分别为两个 \(1\) 所在的行 .

对于 1 情况, 将 \(i_1 \to i_2 + n\), \(i_2 \to i_1 + n\).

对于 2 情况, 将 \(i_1 \to i_2\), \(i_1 + n \to i_2 + n\).

最后如果存在 \(i_1\)\(i_1 + n\) 在同一连通块内, 那么就无解.

否则答案即为 \(2^{\frac{连通块数量}{2}}\).

#include "iostream"
#include "numeric"
#include "cstring"

using namespace std;

constexpr int N = 1e6 + 10, mod = 1e9 + 7;

#define int long long

int n, _n, m, fa[N];
string s[N];

// DSU
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

void merge(int x, int y) {
	x = find(x), y = find(y);
	if (x ^ y) fa[x] = y;
}

void init() {
	cin >> n >> m, _n = n << 1;
	iota(fa, fa + _n + 1, 0);
	for (int i = 1; i <= n; ++i) 
		cin >> s[i], s[i] = ' ' + s[i];
}

basic_string<int> lp, rp;

int qpow(int x, int y) {
	int ans = 1;
	for (; y; y >>= 1, x = x * x % mod) if (y & 1) ans = ans * x % mod;
	return ans;
}

void calculate() {
	for (int j = 1, sum; j <= m; ++j) {
		sum = 0;
		for (int i = 1; i <= n; ++i) sum += (s[i][j] == '1');
		if (sum > 2)
			return cout << 0 << '\n', void();
	}
	for (int l = 1, r = m; l <= r; ++l, --r) {
		lp.clear(), rp.clear();
		for (int i = 1; i <= n; ++i)
			if (s[i][l] == '1') lp += i;
		for (int i = 1; i <= n; ++i)
			if (s[i][r] == '1') rp += i;
		if (lp.size() == 2)
			merge(lp[0], lp[1] + n), merge(lp[1], lp[0] + n);
		if (rp.size() == 2)
			merge(rp[0], rp[1] + n), merge(rp[1], rp[0] + n);
		for (int u : lp) for (int v : rp)
			merge(u, v), merge(u + n, v + n);
	}
	for (int i = 1; i <= n; ++i)
		if (find(i) == find(i + n))
			return cout << 0 << '\n', void();
	int ans = 0;
	for (int i = 1; i <= _n; ++i)
		ans += (find(i) == i);
	cout << qpow(2, ans >> 1) << '\n';
}

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

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	int t;
	cin >> t;
	while (t--)
		solve();
	return 0;
}

T2

算法

数学推导.

思路

观察到 \(a_i - a_{i - 1} = 1 = i - (i - 1) \Rightarrow a_i - i = a_{i - 1} - (i - 1)\).

所以在输入时 \(a_i \gets a_i - i\), 那么问题就转化为求最长的相等子数组.

考虑将一段区间 \([l,r]\) 内的数变成相等的最小代价, 根据初中数学知识, 将所有数变为该区间的中位数代价一定最小.

因为 \(1 \le n \le 10^5\), \(\mathcal{O}(n^2)\) 枚举区间行不通, 改为用双指针维护即可(因为一个合法区间的子区间一定也是合法的).

在实现上, 我们开两个 multiset 维护中位数左右侧的数, 再用双指针边扫边更新答案即可.

时间复杂度 \(\mathcal{O}(n \log n)\).

#include "iostream"
#include "set"

using namespace std;

constexpr int N = 1e5 + 10;

#define int long long

int n, k, a[N];

void init() {
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
		cin >> a[i], a[i] -= i;
}

int suml = 0, sumr = 0;
multiset<int> sl, sr;

bool check() {
	int mid = *sr.begin();
	return (mid * (int)sl.size() - suml) + (sumr - mid * (int)sr.size()) <= k;
}

void push(int x) {
	if (sr.empty())
		return sumr += x, sr.insert(x), void();
	int mid = *sr.begin();
	if (x >= mid) {
		sumr += x, sr.insert(x);
		if ((int)sr.size() - (int)sl.size() > 1) {
			mid = *sr.begin();
			sumr -= mid, suml += mid;
			sr.erase(sr.begin()), sl.insert(mid);
		}
	}
	else {
		suml += x, sl.insert(x);
		if ((int)sl.size() - (int)sr.size() >= 1) {
			mid = *sl.rbegin();
			sumr += mid, suml -= mid;
			sl.erase(--sl.end()), sr.insert(mid);
		}
	}
}

void pop(int x) {
	int mid = *sr.begin();
	if (x >= mid) {
		sr.erase(sr.find(x)), sumr -= x;
		if ((int)sl.size() - (int)sr.size() >= 1) {
			mid = *sl.rbegin();
			sumr += mid, suml -= mid;
			sl.erase(--sl.end()), sr.insert(mid);
		}
	}
	else {
		sl.erase(sl.find(x)), suml -= x;
		if ((int)sr.size() - (int)sl.size() > 1) {
			mid = *sr.begin();
			sumr -= mid, suml += mid;
			sr.erase(sr.begin()), sl.insert(mid);
		}
	}
}

void calculate() {
	suml = sumr = 0, sl.clear(), sr.clear();
	int ans = 1;
	for (int l = 1, r = 0; l <= n; ++l) {
		while (r < n) {
			push(a[++r]);
			if (!check()) {
				pop(a[r--]);
				break;
			}
		}
		ans = max(ans, r - l + 1);
		pop(a[l]);
	}
	cout << ans << '\n';
}

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

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	int t; cin >> t;
	while (t--)
		solve();
	return 0;
}

T4

算法

双指针, 贝祖定理.

思路

考虑对每一个 \(x\) 进行求解. 一个 \(x\) 只会在一个数列中出现至多 1 次, 那么其周期可以表示为 \(kx + b\).

根据贝祖定理, \(k_1 x - k_2 y = b_1 - b_2\) 有解 \(\iff \gcd(k_1, k_2) \mid (b_1 - b_2)\), 那么我们对于一段连续区间判断是否合法只需要判断其是否两两有解即可.

实现考虑双指针, 找到最长合法区间, 对每个模数记录出现的位置及余数即可.

#include "iostream"
#include "cstring"
#include "numeric"

using namespace std;

constexpr int N = 1e5 + 10, M = 45;

int n, m, c;
int l[N], a[N][M];
int lst[N], f[N], ans[N], p[N][M], q[N][M];

void init() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", l + i);
		for (int j = 1; j <= l[i]; ++j) scanf("%d", a[i] + j);
	}
	for (int i = 0; i <= m; ++i) {
		lst[i] = -1, f[i] = ans[i] = 0;
		for (int j = 1; j <= 40; ++j) p[i][j] = q[i][j] = 0;
	}
}

bool check(int a, int b, int c, int d) { return (a - c) % gcd(b, d); }

void calculate() {
	for (int i = 1; i <= n; ++i) for (int j = 1; j <= l[i]; ++j) {
		int v = a[i][j];
		if (lst[v] ^ (i - 1)) f[v] = i;
		lst[v] = i;
		for (int k = 1; k <= 40; ++k) 
			if (q[v][k] >= f[v] and check(j, l[i], p[v][k], k)) f[v] = q[v][k] + 1;
		p[v][l[i]] = j, q[v][l[i]] = i;
		ans[v] = max(ans[v], i - f[v] + 1);
	}
	for (int i = 1; i <= m; ++i) printf("%d ", ans[i]);
	puts("");
}

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

int main() {
	int t; scanf("%d", &t);
	while (t--) solve();
	return 0;
}
posted @ 2025-01-07 20:24  Steven1013  阅读(20)  评论(1)    收藏  举报