总结:赛时一题不会,赛后光速 AK
A
namespace Triple_Light {int a[N];
void run() {int T = read();while (T--) {int n = read();int buc[30] = {0};for (int i = 1; i <= n; ++i) {int x = read();++buc[x];}int cnt = 0;for (int i = 1; i <= n; ++i) {cnt += buc[i] / 2;}cout << cnt << '\n';}
} }
B
首先可以知道 \(n\times m=k-2\),又因为 \(k-2\) 很小,所以直接暴力分解 \(k-2\) 的因数,判断是否可以拆分为 \(h\times w=k-2\) 且满足 \(a\) 中恰好存在 \(h\) 和 \(w\) 即可。特殊的,需要特判 \(h=w\) 的情况。
总时间复杂度为 \(O(k)\)。
namespace Triple_Light {int a[N], vis[N];
void run() {int T = read();while (T--) {int n = read();for (int i = 1; i <= n; ++i) {vis[i] = 0;}for (int i = 1; i <= n; ++i) {a[i] = read();++vis[a[i]];}int kx = n - 2;for (int i = 1; i * i <= kx; ++i) {if (kx % i == 0) {int a = kx / i, b = i;if (a == b) {if (vis[a] >= 2) {cout << a << ' ' << b << '\n';break;}} else {if (vis[a] && vis[b]) {cout << a << ' ' << b << '\n';break;}}}}}
} }
C
顶好玩的构造题。首先大于 \(4\) 的偶数肯定是合数,也就是说一堆奇数和偶数肯定是满足条件的。问题是奇数和偶数之间的地方,考虑让其的和可以被 \(3\) 整除,直接令奇数最后一个是最大的奇数,然后暴力枚举合法的可以衔接的偶数即可。对于 \(n\) 很小的情况可以直接暴搜全排列,总时间复杂度为 \(O(n)\)。
namespace Triple_Light {int a[N], vis[N];
void run() {int T = read();while (T--) {int n = read();for (int i = 1; i <= n; ++i) {vis[i] = 0;}for (int i = 1; i <= n; ++i) {a[i] = read();++vis[a[i]];}int kx = n - 2;for (int i = 1; i * i <= kx; ++i) {if (kx % i == 0) {int a = kx / i, b = i;if (a == b) {if (vis[a] >= 2) {cout << a << ' ' << b << '\n';break;}} else {if (vis[a] && vis[b]) {cout << a << ' ' << b << '\n';break;}}}}}
} }
D
首先若跨越了第 \(i-1\) 个障碍,则肯定能够到达 \(l_i-1\)。如果不选能从 \(l_i-1\) 直接到达 \(r_i+1\) 那么肯定不选,否则暴力枚举当前所有可以选择的装备并贪心的选择可以移动距离最大的装备,直到当前可以从 \(l_i-1\) 跳到 \(r_i+1\) 为止。维护当前可选的装备可以用堆维护,时间复杂度为 \(O(n\log n)\)。
namespace Triple_Light {int l[N], r[N], x[N], v[N];
void run() {int T = read();while (T--) {int n = read(), m = read(), L = read();for (int i = 1; i <= n; ++i) {l[i] = read();r[i] = read();}for (int i = 1; i <= m; ++i) {x[i] = read();v[i] = read();}int pos = 1;priority_queue<int> q;int now = 1, cnt = 0, ok = 1;for (int i = 1; i <= n; ++i) {while (pos <= m && x[pos] < l[i]) {q.push(v[pos]);++pos;}int len = r[i] - l[i] + 2;if (now < len) {while (q.size()) {int t = q.top();q.pop();now += t;++cnt;if (now >= len) {break;}}if (now < len) {ok = 0;break;}}}if (ok) {cout << cnt << '\n';} else {cout << "-1\n";}}
} }
E
首先考虑手摸一下寻找线索,可以发现若每一次都按照顺序询问一个前缀,则在一开始询问到一堆 \(0\) 之后在 \(i\) 位置得到了一个非零数 \(x\),则:
- \(a_x=1\)
- \(a\) 数组中存在一个长度为 \(x\) 的全 \(1\) 前缀。
- 其余在 \([1,x]\) 前缀内的位置的值全都为 \(0\)。
然后对于后面仍然询问前缀,若当前询问到前缀和上一次询问前缀的答案相同,则当前位结尾对答案没有贡献,即当前位为 \(0\),否则因为前面一定存在 \(0\)(否则第一次不能询问出非 \(0\) 值),当前位对答案产生贡献,则当前位为 \(1\)。
特殊的,若没有找到一个非 \(0\) 的前缀,则输出 IMPOSSIBLE
。
总时间复杂度为 \(O(n)\),询问 \(n-1\) 次,符合题目条件。
namespace Triple_Light {int a[N];
void run() {int T = read();while (T--) {int n = read(), pos = 1;for (int i = 1; i <= n; ++i) {a[i] = -1;}int la = 0;for (int i = 2; i <= n; ++i) {cout << "? " << pos << ' ' << i << endl;int o ; cin >> o;int to = o;if (o != la) {if (!la) {o -= la;a[i] = 1;int j, k;for (j = i - 1, k = 1; k <= o; --j, ++k) {a[j] = 0;}for (; j >= pos; --j) {a[j] = 1;}la = to;} else {a[i] = 1;la = to;}} else if (la) {// cout << "qwq " << i << '\n';a[i] = 0;}}if (count(a + 1, a + n + 1, -1)) {cout << "! IMPOSSIBLE" << endl;} else {cout << "! ";for (int i = 1; i <= n; ++i) {cout << a[i];}cout << endl;}}
} }
F
笑点解析:已被删除
考虑经典套路,二分一个答案 \(x\) 判断其是否合法。考虑二分完之后计算每一个怪物可以在攻击 \(x\) 轮之后去世的区间 \([L_i,R_i]\),然后在数轴上建立扫描线模型,设 \((x,o)\) 表示 \(x\) 位置中线段数量会增加 \(o\)(若 \(o\) 为负数则为减少 \(-o\)),因此只需要添加 \((L_i,1)\) 和 \((R_i+1,-1)\)。然后对扫描线按照坐标从小到大排序并判断是否存在一个前缀满足该前缀内所有 \(o\) 的和超过了 \(k\) 即可。
总时间复杂度为 \(O(n\log n)\),可以通过。
namespace ttq012 {int h[N], x[N];
void run() {int T = read();while (T--) {int n = read(), m = read(), k = read();for (int i = 1; i <= n; ++i) {h[i] = read();}for (int i = 1; i <= n; ++i) {x[i] = read();}int l = 1, r = inf, best = -1;while (l <= r) {int mid = l + r >> 1;vector<pair<int, int>> event;for (int i = 1; i <= n; ++i) {int pwk = (h[i] + mid - 1) / mid;if (pwk <= m) {int L = x[i] - (m - pwk), R = x[i] + (m - pwk);event.eb(L, 1), event.eb(R + 1, -1);}}sort(event.begin(), event.end(), [&](auto l, auto r) {return l.first < r.first || l.first == r.first && l.second < r.second;}) ;int pref = 0, ok = 0;for (auto &[pos, vx] : event) {if ((pref += vx) >= k) {ok = 1;break;}}if (ok) {best = mid, r = mid - 1;} else {l = mid + 1;}}cout << best << '\n';}
} }
G
看到 \(\gcd\neq 1\) 可以快速想到莫比乌斯函数容斥答案,于是设 \(f_i\) 表示从第 \(1\) 个数移动到第 \(i\) 个数的不同路径数量,显然有 dp 转移式:
但是直接转移时间复杂度为 \(O(n^2\log n)\),显然无法通过。考虑设 \(g_i\) 表示在值域上,当前为 \(i\) 的倍数的 \(a_i\) 所代表的 \(f_i\) 的值的和,则可以使用莫比乌斯函数对答案容斥,有:
- \(f_1=1\)
- \(f_i=-\sum\limits_{x\mid a_i}\mu(x)g_x\)(\(i>1\))
- \(g_x\leftarrow g_x+f_i\),\(x\mid a_i\)
欧拉线性筛出 \(\mu\) 函数的值,然后直接 dp 时间复杂度为 \(O(n^\frac{3}{2})\),使用 Pollard-Rho 分解质因数可以优化到 \(O(n^\frac{5}{4})\)。但是可以一遍埃筛得到每一个数的因数,做到 \(O(n\log n)\)。
namespace ttq012 {int a[N], mu[N], idx, pr[N], isp[N];
void sieve(int n) {isp[1] = 1, mu[1] = 1;for (int i = 2; i < n; ++i) {if (!isp[i]) {pr[++idx] = i, mu[i] = -1;}for (int j = 1; j <= idx && i * pr[j] < n; ++j) {int k = i * pr[j];isp[k] = 1;if (i % pr[j] == 0) {mu[k] = 0;break;} else {mu[k] = -mu[i];}}}
}
int f[N], g[N];
void run() {sieve(N);int n = read();for (int i = 1; i <= n; ++i) {a[i] = read();}for (int i = 1; i <= n; ++i) {vector<int> fact;for (int j = 2; j * j <= a[i]; ++j) {if (a[i] % j == 0) {fact.eb(j);if (j * j != a[i]) {fact.eb(a[i] / j);}}}fact.eb(a[i]);if (i == 1) {f[i] = 1;} else {for (auto &x : fact) {f[i] = (f[i] - mu[x] * g[x] % mod + mod) % mod;}}for (auto &x : fact) {g[x] = (g[x] + f[i]) % mod;}}cout << f[n] << '\n';
} }