2024牛客暑期多校训练营8
写在前面
比赛地址:https://ac.nowcoder.com/acm/contest/81603
以下按个人难度向排序。
dztlb 大神回去补办身份证了,于是单刷,还是打的像史。
哎呦 E 的优化方向错了把 108 优化没了变成跑满的根号我也是真牛逼
K
签到,DP。
发现合成合法字符串过程中往左右加字符是无所谓的。字符串合法等价于字符串由若干 ava
与 avava
按顺序连接得到,于是直接 DP 判断即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, f[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::string s; std::cin >> s;
n = s.length();
s = "$" + s;
for (int i = 1; i <= n; ++ i) f[i] = 0;
f[0] = 1;
int flag = 0;
for (int i = 1; i <= n; ++ i) {
if (s[i] != 'a' && s[i] != 'v') flag = 1;
if (i >= 3 && s.substr(i - 2, 3) == "ava") f[i] |= f[i - 3];
if (i >= 5 && s.substr(i - 4, 5) == "avava") f[i] |= f[i - 5];;
}
if (flag || f[n] == 0) std::cout << "No\n";
else std::cout << "Yes\n";
}
return 0;
}
A
数论。
哈哈这题真熟写的时候就感觉好像什么时候肯定写过一样,果然是做过的原:CF1627D。
由算数基本定理有 \(\gcd\) 的传递性:\(\gcd(x, \gcd(y, z)) = \gcd(\gcd(x, y), z) = \gcd(x, y, z)\),实际上问题变为求从原数组中任意多个元素的 \(\gcd\) 的种类数(满足不在原数组中),若新增种类数为奇数则先手必胜,否则后手必胜。
套路地枚举 \(i = \gcd\),检查是否有原数组中 \(i\) 的倍数的 \(\gcd =i\),实现时仅需枚举 \(i\) 的在原数组中出现的所有倍数,并检查它们除掉 \(i\) 后是否有 \(\gcd=1\) 即可。
总时间复杂度 \(O(v\ln v)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, nowtime, a[kN], tim[kN], cnt[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
int maxa = 0;
++ nowtime;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
maxa = std::max(maxa, a[i]);
if (tim[a[i]] != nowtime) tim[a[i]] = nowtime, cnt[a[i]] = 0;
cnt[a[i]] = cnt[a[i]] + 1;
}
int ans = 0;
for (int d = 1; d <= maxa; ++ d) {
if (tim[d] != nowtime) tim[d] = nowtime, cnt[d] = 0;
if (cnt[d]) continue;
int c = 0, dd = 0;
for (int i = 2; d * i <= maxa; ++ i) {
if (tim[d * i] != nowtime) tim[d * i] = nowtime, cnt[d * i] = 0;
if (cnt[d * i]) {
c += cnt[d * i];
if (dd == 0) dd = i;
else dd = std::__gcd(dd, i);
}
}
if (dd == 1 && c >= 2) ++ ans;
}
std::cout << ((ans % 2 == 1) ? "dXqwq" : "Haitang") << "\n";
}
return 0;
}
E
数论,筛法。
实际区间筛板题!
发现有 \(S(m)\le 9\times 12\),上界在 \(m = 10^{12} - 1\) 时取到,于是考虑直接枚举 \(S(m) = n \bmod m\) 并检查有多少 \(m\) 满足 \(m = S(m)\) 且 \(m | n - S(m)\)。
考虑暴力实现,直接对 \(n-108 \sim n - 1\) 这 108 个数进行因数分解,并检查所有因子能否作为有贡献的 \(m\) 即可。大力上 Pollard Rho 质因数分解之后枚举所有因数可以卡过去,但这太呃呃了一点也不优美。
发现 \([n - 108, n - 1]\) 构成一段连续的区间,且 \(n\le 10^{12}\),它们至多仅有一个大于 \(\sqrt{n} = 10^6\) 的质因数,于是考虑区间筛对他们进行质因数分解。具体地:
- 预处理不大于 \(\sqrt{n} = 10^6\) 的所有质数。
- 对于每次询问,枚举 \(\sqrt{n}=10^6\) 的所有因数 \(d\),求得它们在区间 \([n - 108, n - 1]\) 中的所有倍数,并对这些倍数不断除 \(d\) 并记录次数。
- 枚举结束后,特判 \([n - 108, n - 1]\) 中所有数的至多一个大于 \(\sqrt{n} = 10^6\) 的质因数。
即可在 \(O\left(\frac{\sqrt{n}}{\log n}\right)\) 时间复杂度内完成对所有数的质因数分解,之后再大力枚举因数并检查即可,检查部分需要枚举所有因数并大力计算 \(S(m)\),时间复杂度为 \(O(d(n) \log v)\) 级别,\(\log v\le 12\)。
总时间复杂度 \(O\left(T\left(\frac{\sqrt{n}}{\log n} + d(n) \log v\right)\right)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const int N = 1e6;
//=============================================================
LL n, ans;
int pnum;
LL p[kN];
bool vis[kN];
std::vector<LL> value, pri[108], cnt[108];
//=============================================================
LL calc(LL m_) {
LL ret = 0;
while (m_ > 0) {
ret += m_ % 10ll;
m_ /= 10ll;
}
return ret;
}
void init() {
for (int i = 2; i <= N; ++ i) {
if (!vis[i]) p[++ pnum] = i;
for (int j = 2; i * j <= N; ++ j) {
vis[i * j] = 1;
}
}
}
void dfs(int id_, LL now_, LL d_, LL sm_) {
if (now_ >= pri[id_].size()) {
if (d_ > sm_ && calc(d_) == sm_) ++ ans;
return ;
}
for (int i = 0; i <= cnt[id_][now_]; ++ i) {
dfs(id_, now_ + 1, d_, sm_);
d_ *= pri[id_][now_];
}
}
void solve() {
value.clear();
for (int i = 108; i; -- i) {
value.push_back(n - i);
pri[i - 1].clear();
cnt[i - 1].clear();
}
for (int i = 1; i <= pnum; ++ i) {
if (p[i] >= n) break;
for (LL j = ceil(1.0 * (n - 108) / p[i]); j <= n / p[i]; ++ j) {
if (n - 108 <= p[i] * j && p[i] * j < n) {
LL v = p[i] * j - n + 108;
value[v] /= p[i];
pri[v].push_back(p[i]);
cnt[v].push_back(1);
while (value[v] % p[i] == 0) {
++ cnt[v].back();
value[v] /= p[i];
}
}
}
}
for (int i = 0; i < 108; ++ i) {
if (value[i] != 1) pri[i].push_back(value[i]), cnt[i].push_back(1);
dfs(i, 0, 1, 108 - i);
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
init();
int T; std::cin >> T;
while (T --) {
std::cin >> n;
ans = 0;
if (n <= 108) {
for (LL m = 1; m <= n; ++ m) if (n % m == calc(m)) ++ ans;
} else {
solve();
}
std::cout << ans << "\n";
}
return 0;
}
J
构造。
和题解一样的构造方法,这里重点记录下实现。
首先考虑特殊情况,发现对于 \(1, 2, \cdots, n\) 可以构成 \(n-3\) 个三角形而达到上界;\(m=n-2\) 时无解因为 1 与任何数都无法构成三角形;令 \(d = \left\lfloor\frac{n}{3}\right\rfloor\),手玩下发现当有类似如下斐波那契数列的形式时可取到 \(m=0\):
若 \(n\) 不为 3 的倍数,仅需在两侧分别加上 \(n - 2 = 3d + 1\) 和 \(n-1 = 3d+ 2\) 即可:
然后考虑 \(0<m< n-3\) 时如何构造。一个显然的想法是将上述两种分别达到上界和下界的做法拼起来即可。首先选择 \(1\sim n-m\) 构造成 \(m=0\) 的形式并放在数列开头,然后将剩下的 \(n-m+1 \sim n\) 顺序排列到数列后面即可。
发现此时 \((n - m)\bmod 3 \not= 1\) 的情况即可顺利解决,可以保证前 \(n-m\) 个数无法构成任何三角形,后面 \(m+2\) 个数均可构成三角形;但对于 \((n - m)\bmod 3 = 1\) 的情况,可能导致恰好有 \((d + 1) + (2d + 1) \le n-m+1\),此时数列中的三角形数还缺不多于 2 个,需要考虑怎么再凑出一些三角形。
我的做法是考虑到 1 和任何数都无法构成三角形,且在上述 \(m=0\) 的构造中 1 并不位于两端,导致阻碍了至少 3 个三角形的构成。又发现 1 左移一位可以增加 1 个三角形,冒泡到最左端可以增加 2 个三角形,特判下即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e5 + 10;
//=============================================================
int n, m, ans[kN], vis[kN];
//=============================================================
int calc() {
int ret = 0;
for (int i = 1; i <= n - 2; ++ i) {
std::vector<int> tri{ans[i], ans[i + 1], ans[i + 2]};
std::sort(tri.begin(), tri.end());
if (tri[0] + tri[1] > tri[2]) ++ ret;
}
return ret;
}
void solve() {
for (int i = 1; i <= n; ++ i) vis[i] = 0;
std::vector<int> temp;
int d = (n - m) / 3;
if (3 * d + 1 <= (n - m)) temp.push_back(3 * d + 1);
for (int i = 0; i <= d - 1; ++ i) {
for (int j = 1; j <= 3; ++ j) {
temp.push_back(j * d - i);
}
}
if (3 * d + 2 <= (n - m)) temp.push_back(3 * d + 2);
int p = 0;
for (auto x: temp) ans[++ p] = x;
if (m == 0) return ;
for (int i = n - m + 1; i <= n; ++ i) ans[++ p] = i;
int pos1 = 0;
for (int i = 1; i <= n; ++ i) if (ans[i] == 1) pos1 = i;
if ((n - m) % 3 == 1) {
int c = calc();
if (c == m - 1) {
std::swap(ans[pos1], ans[pos1 - 1]);
} else if (c == m - 2) {
for (int i = pos1; i; -- i) std::swap(ans[i], ans[i - 1]);
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
if (m >= n - 2) {
std::cout << -1 << "\n";
continue;
}
solve();
for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
std::cout << "\n";
}
return 0;
}
/*
*/
D
大模拟。
鉴定为玩赛马娘 Pretty Derby 玩的哈哈坐牢做红温了还要出成题丢给五千个人一块坐牢实在是拖累那之鉴!
幸好我是高贵的赛马娘 Pretty Derby 玩家赛时不看题就直接秒了,但是单刷没心情也没时间写,于是直接跑路。
G
DP。
有趣的 DP 状态设计优化。
I
数据结构。
写在最后
学到了什么:
- E:区间筛!
- J:考虑分别达到上下界的构造,然后考虑构造的结合。
唉单刷太累了妈的两场牛客都打得一坨希望今天杭电能少吃点。