模拟赛 1
我借你梦想的时间
让你走得足够遥远
我让你心中的山川
跋涉去不用归还
清风浪海 翘马花剑
T1 MG loves gold
统计共有几段不存在重复元素的序列。用 map 的时间复杂度为 \(\mathcal{O}(n\log n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
map<int, bool> mp;
int T, n;
int a[N];
void solve() {
scanf("%d", &n); mp.clear();
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int ans = 1;
for (int i = 1; i <= n; i++) {
if (mp[a[i]]) ans++, mp.clear();
mp[a[i]] = 1;
}
cout << ans << endl;
}
int main() {
scanf("%d", &T);
while (T--) solve();
return 0;
}
// START AT 2025 / 04 / 09 22 : 10 : 09
T2 MG loves apple
记 \(rem(i)\) 表示数字卡片中对 \(3\) 取模余数为 \(i\) 的个数,在数字卡片中分别取走 \(a,b,c\) 个对 \(3\) 取模余数为 \(0, 1, 2\) 的卡片。显然需要 \(a \le rem(0), b \le rem(1), c \le rem(2)\)。
则当 \(b + 2c \equiv rem(1) + 2rem(2)\pmod 3\bigwedge a + b + c = k\) 时,满足剩下的卡片按原有顺序排列后形成的数是 \(3\) 的倍数。
但是可能不满足没有前导零。
对此,我们有贪心:取走 \(a\) 个对 \(3\) 取模余数为 \(0\) 的卡片时,先从前往后取走 \(0\),然后随便取都行,分别取走 \(b,c\) 个对 \(3\) 取模余数为 \(1, 2\) 的卡片时,从后往前取。
如果模拟复杂度会爆掉。
但是其实只需要知道有无前导零,事实上只需要满足三个条件中任一个即可。
- 找到第一个满足模以 \(3\) 等于 \(0\) 且不等于 \(0\) 的数,统计它前面有几个 \(0\),记为 \(x\) 个,如果 \(a \ge x\),一定不会有前导零。
- 找到第一个 \(0\),分别确定其前是否有模以 \(3\) 为 \(1, 2\) 的数,如果有且 \(b \lt rem(1)\) 或者 \(c \lt rem(2)\),一定不会有前导零。
复杂度为 \(\mathcal{O}(n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N], T;
string s;
void solve() {
int n, k;
cin >> n >> k >> s;
s = "-" + s;
int s0 = 0, s1 = 0, s2 = 0;
for (int i = 1; i <= n; i++) {
a[i] = s[i] - '0';
if (a[i] % 3 == 1) a[i] = 1, s1++;
else if (a[i] % 3 == 2) a[i] = 2, s2++;
else s0++, a[i] = a[i] ? 3 : 0;
}
int ans = (s1 + s2 * 2) % 3;
int f = 0, a3 = 0, f1 = 0, f2 = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == 0) a3++;
if (a[i] == 3) break;
}
for (int i = 1; i <= n; i++) {
if (a[i] == 0) break;
if (a[i] == 1) f1 = 1;
if (a[i] == 2) f2 = 1;
}
for (int C = 0; C <= s2 && C <= k; C++) {
int B = ((ans - C * 2) % 3 + 3) % 3;
for (; B <= s1 && B + C <= k; B += 3) {
int A = k - B - C;
if (A <= s0) {
if (A >= a3) f = 1;
else if (B < s1 && f1) f = 1;
else if (C < s2 && f2) f = 1;
}
if (f) goto ANS;
}
}
ANS :;
if ((n == k + 1) && s0) f = 1;
if (f) puts("yes");
else puts("no");
}
int main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
cin >> T;
while (T--) solve();
return 0;
}
T3 MG loves string
每个字母经过多少次变换变回原字母可求。容易发现一种情况对答案的贡献是该情况下字符串中所有字母变换次数的 \(\mathbf{lcm}\)。
那么可以将变换次数相同的数看作一类,可以证明最多有 \(6\) 类。
证明
字母变换过程中经过的了一个环,这说明环上的字母变换次数相同。
若有字母变换 \(t\) 次,则至少有 \(t\) 个字母变换 \(t\) 次。
因为 \(1 + 2 + 3 + 4 + 5 + 6 + 7 > 26\),故最多有 \(6\) 类。
问题转化为将 \(n\) 个元素分进 \(m\) 个集合,很容斥。
复杂度为 \(\mathcal{O}(2^6\log n + 3^6)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
using i64 = long long;
int T, n;
string s;
vector<pair<int, int>> loop;
int Pow(int a, int b) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = 1LL * a * res % mod;
a = 1LL * a * a % mod;
}
return res;
}
void calc() {
loop.clear();
vector<int> c(27); vector<bool> vis(27);
map<int, int> mp;
for (int i = 1; i <= 26; i++) c[i] = s[i - 1] - 'a' + 1;
for (int i = 1; i <= 26; i++) if (!vis[i]) {
int u = c[i], cnt = 1; vis[i] = 1;
while (u != i) cnt++, vis[u] = 1, u = c[u];
mp[cnt]++;
}
for (auto e : mp) loop.push_back(e);
}
int lcm(int a, int b) {
return (int)(1LL * a * b / __gcd(a, b) % mod);
}
int inex(vector<int>& vec) {
int ans = 0;
for (int i = 0; i < (1 << vec.size()); i++) {
int num = 0, sum = 0;
for (int j = 0; j < vec.size(); j++)
if (i >> j & 1) num++, sum += vec[j];
int o = ((vec.size() - num) % 2 == 0 ? 1 : -1);
ans = ((1LL * ans + (1LL * Pow(sum, n) * o % mod + mod) % mod) % mod + mod) % mod;
}
return ans;
}
void solve() {
cin >> n >> s;
calc();
vector<int> vec; int ans = 0;
for (int i = 1; i <= (1 << loop.size()); i++) {
vec.clear(); int t = 1;
for (int j = 0; j < loop.size(); j++) if (i >> j & 1) {
vec.push_back(loop[j].first * loop[j].second);
t = lcm(t, loop[j].first);
}
if (vec.size() > n) continue;
ans = (1LL * ans + 1LL * inex(vec) * t % mod) % mod;
}
cout << ans << endl;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
cin >> T;
while (T--) solve();
return 0;
}
// START AT 2025 / 04 / 12 23 : 13 : 36
T4 MG loves set
设选出子序列为集合 \(S\),根据题目,需要 $(\sum \limits_{i = 1} ^{|S|}S_i) ^ 2 \ge \sum \limits_{i = 1} ^{|S|} S_i^2 $,移项,得当 \(\sum\limits_{1 \le i \lt j \le n} S_iS_j \ge 0\) 时满足条件。
很容易暴力枚举子集检查,单次时间复杂度为 \(\mathcal{O}(2^n)\),会爆。
考虑 meet in middle,暴力枚举前一半和后一半的子集,求出各子集的元素和 \(x\) 和两两元素间的乘积和 \(y\),并用 \((x,y)\) 表示子集,对于前一半的某个子集 \((x, y)\),后一半的某个子集 \((z, w)\)。显然这两个子集能满足条件当且仅当 \(x \times z + y + w \ge 0\)。
转化一下,为:\(w \ge -x \times z - y\),这是一条直线的上方或在直线上。那么可以用 K-D Tree 解决。
具体来说,将一半的节点丢到 K-D Tree 上,枚举另一半的节点确定直线,根据 K-D Tree 中节点代表的矩形的四个顶点判断与该直线的关系。
因为 \(2 ^ {n/ 2}\) 个节点全部预先给出,所以 K-D Tree 没有重构操作,时间复杂度应该是 \(\mathcal{O}(T\times2^{\frac{n}{2}}n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
x = 0; int f = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f; read(arg...);
}
void write() {}
template<typename T> void write(T x) {
if (x < 0) { putchar('-'), x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int N = 35;
const int M = (1 << 20);
const int K = 2;
struct node { i64 dim[2]; } A[M], B[M];
struct KD_Tree {
i64 Min[2], Max[2], dim[2];
int sz;
} tr[M];
int T, n, a[N], cnt, now;
bool flag[M];
bool cmp(node a, node b) { return a.dim[now] < b.dim[now]; }
void up(int rt) {
if (flag[rt << 1]) {
for (int k = 0; k <= 1; k++) {
tr[rt].Min[k] = min(tr[rt].Min[k], tr[rt << 1].Min[k]);
tr[rt].Max[k] = max(tr[rt].Max[k], tr[rt << 1].Max[k]);
}
tr[rt].sz += tr[rt << 1].sz;
}
if (flag[rt << 1 | 1]) {
for (int k = 0; k <= 1; k++) {
tr[rt].Min[k] = min(tr[rt].Min[k], tr[rt << 1 | 1].Min[k]);
tr[rt].Max[k] = max(tr[rt].Max[k], tr[rt << 1 | 1].Max[k]);
}
tr[rt].sz += tr[rt << 1 | 1].sz;
}
tr[rt].sz++;
}
void build(int l, int r, int dep, int rt) {
if (l > r) return;
int mid = (l + r) >> 1, d = dep % K;
now = d;
flag[rt] = 1; flag[rt << 1] = flag[rt << 1 | 1] = 0;
nth_element(B + l, B + mid, B + r + 1, cmp);
for (int k = 0; k <= 1; k++) tr[rt].dim[k] = tr[rt].Max[k] = tr[rt].Min[k] = B[mid].dim[k];
build(l, mid - 1, dep + 1, rt << 1), build(mid + 1, r, dep + 1, rt << 1 | 1);
up(rt);
}
int check(i64 x1, i64 y1, i64 x2, i64 y2) {
return (x1 *x2 + y1 + y2 >= 0);
}
int count(node a, int p) {
int res = 0;
res += check(tr[p].Min[0], tr[p].Min[1], a.dim[0], a.dim[1]);
res += check(tr[p].Min[0], tr[p].Max[1], a.dim[0], a.dim[1]);
res += check(tr[p].Max[0], tr[p].Min[1], a.dim[0], a.dim[1]);
res += check(tr[p].Max[0], tr[p].Max[1], a.dim[0], a.dim[1]);
return res;
}
int query(node a, int p) {
if (!flag[p]) return 0;
if (count(a, p) == 4) return tr[p].sz;
int res = check(a.dim[0], a.dim[1], tr[p].dim[0], tr[p].dim[1]);
if (count(a, p << 1)) res += query(a, p << 1);
if (count(a, p << 1 | 1)) res += query(a, p << 1 | 1);
return res;
}
void solve() {
memset(A, 0, sizeof(A));
memset(B, 0, sizeof(B));
memset(a, 0, sizeof(a));
memset(tr, 0, sizeof(tr));
read(n);
for (int i = 1; i <= n; i++) read(a[i]);
int mid = n / 2;
cnt = 0;
for (int i = 0; i < (1 << mid); i++) {
A[cnt].dim[0] = A[cnt].dim[1] = 0;
for (int k = 1; k <= mid; k++) {
if (i >> (k - 1) & 1) {
A[cnt].dim[1] += A[cnt].dim[0] * a[k];
A[cnt].dim[0] += a[k];
}
}
cnt++;
}
int cnt1 = cnt;
cnt = 0;
for (int i = 0; i < (1 << n); i += (1 << mid)) {
B[cnt].dim[0] = B[cnt].dim[1] = 0;
for (int k = mid + 1; k <= n; k++) {
if (i >> (k - 1) & 1) {
B[cnt].dim[1] += B[cnt].dim[0] * a[k];
B[cnt].dim[0] += a[k];
}
}
cnt++;
}
build(0, cnt - 1, 0, 1);
int ans = -1;
for (int i = 0; i < cnt1; i++) ans += query(A[i], 1);
write(ans); puts("");
}
int main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
read(T);
while (T--) solve();
return 0;
}
// START AT 2025 / 04 / 27 22 : 42 : 23
浙公网安备 33010602011771号