2024牛客暑期多校训练营7
写在前面
比赛地址:https://ac.nowcoder.com/acm/contest/81602#question
以下按个人难度向排序。
dztlb 大神回去补办身份证了,于是单刷,打的像史。
呃呃抽象场。
J
签到。
手玩下发现取定点为两端点时,画出的圆一定可以包含取定点为其他任何点的圆。
于是仅需判断给定点与两端点中一方距离是否不大于 \(L\) 即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const double eps = 1e-9;
//=============================================================
double l, x, y;
//=============================================================
double dis(double x1_, double y1_, double x2_, double y2_) {
return sqrt((x1_ - x2_) * (x1_ - x2_) + (y1_ - y2_) * (y1_ - y2_));
}
//=============================================================
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 >> l >> x >> y;
if (dis(0, 0, x, y) >= l + eps && dis(l, 0, x, y) >= l + eps) {
std::cout << "No\n";
} else if (dis(0, 0, x, y) <= l + eps) {
std::cout << "Yes\n0\n";
} else {
std::cout << "Yes\n" << l << "\n";
}
}
return 0;
}
I
签到。
显然答案是有单调性的,考虑二分答案检查枚举量 \(n=\operatorname{lim}\) 时是否合法。
发现在满足 \(n\ge m\) 的情况下,每进行一轮增殖操作,没有增殖过的机器人数改变量为 \(n\rightarrow n - (m - k)\),则最大增殖轮数显然为 \(d=\left\lfloor\frac{n - m}{m - k}\right\rfloor + 1\),仅需判断是否有 \(n + k\times d\ge h\) 即可。
注意特判 \(m=k\)。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define int long long
//=============================================================
int m, k, h;
//=============================================================
bool check(int mid_) {
if (mid_ >= h) return true;
if (m == k) return mid_ >= m;
if (mid_ < m) return false;
int d = (mid_ - m) / (m - k) + 1;
return (mid_ + d * k) >= h;
}
//=============================================================
signed 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 >> m >> k >> h;
int ans = h, l = 1, r = h;
while (l <= r) {
int mid = (l + r) / 2ll;
if (check(mid)) {
ans = std::min(ans, mid);
r = mid - 1;
} else {
l = mid + 1;
}
}
std::cout << ans << "\n";
}
return 0;
}
K
枚举,DP。
除了恶心人没意思的缝合题呃呃。
手玩下发现有贡献度子序列 \(seq\) 有两种形态:
- \(|seq| \ge 2\times |T|\),此时 \(seq\) 中与 \(T\) 匹配的前后缀是不相交的。
- \(|seq| < 2\times |T|\),此时 \(seq\) 中与 \(T\) 匹配的前后缀有相交部分,说明 \(T\) 的重叠部分的后缀是回文的。
对于第一种 \(seq\),显然取出此类子序列的最优选择,是分别从 \(S\) 的首尾开始匹配得到第一个 \(T\) 构成 \(seq\) 中与 \(T\) 匹配的前后缀。记 \(S\) 中匹配位置分别为 \(l, r\),则 \([l + 1, r - 1]\) 中可以任取本质不同的子序列插入到 \(seq\) 的中间,对该区间 DP 计算本质不同子序列数即可。
对于第二种 \(seq\),发现此类 \(seq\) 数至多仅有 \(|T|\) 种,考虑直接枚举重叠的回文后缀部分的长度 \(l\),并检查 \(S\) 中是否有对应子序列出现即可。对于某个重叠的回文后缀部分的长度 \(l\) 发现仅需保证下列条件即有贡献:
- \(T[m - l + 1:m]\) 是回文的。可以简单地哈希判断。
- 从 \(S\) 的开头匹配前缀 \(T[1:m - l]\) 的位置为 \(p_1\),\(S\) 的尾部匹配 \(T\) 的位置为 \(p_2\),有 \(p_1 < p_2\) 成立。预处理一下各个匹配位置即可。
上述两种 \(seq\) 数量求和即为答案。总时间复杂度 \(O(n+m)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const LL p1 = 998244353;
const LL c1 = 1145141;
const LL p = 1e9 + 7;
//=============================================================
int n, m, next[26], to1[kN][26], to2[kN][26];
std::string s, t, revt;
std::map<int, int> posrevt, post;
LL ans, pow1[kN], ht[kN], hrevt[kN], f[kN];
//=============================================================
LL hash1(LL *h_, int l_, int r_) {
return (h_[r_] - pow1[r_ - l_ + 1] * h_[l_ - 1] % p1 + p1) % p1;
}
bool is_palindrome(int l_, int r_) {
bool ret = hash1(ht, l_, r_) == hash1(hrevt, m - r_ + 1, m - l_ + 1);
return ret;
}
void init() {
pow1[0] = 1;
for (int i = 1; i <= m; ++ i) {
pow1[i] = pow1[i - 1] * c1 % p1;
ht[i] = (c1 * ht[i - 1] + t[i]) % p1;
hrevt[i] = (c1 * hrevt[i - 1] + revt[i]) % p1;
}
for (int i = n; i; -- i) {
for (int j = 0; j < 26; ++ j) to1[i][j] = next[j];
next[s[i] - 'a'] = i;
}
for (int i = 0; i < 26; ++ i) to1[0][i] = next[i];
for (int i = 0; i < 26; ++ i) next[i] = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j < 26; ++ j) to2[i][j] = next[j];
next[s[i] - 'a'] = i;
}
for (int i = 0; i < 26; ++ i) to2[n + 1][i] = next[i];
}
void calc(int l_, int r_) {
int lst[26] = {0};
f[l_ - 1] = 1;
for (int i = l_; i <= r_; ++ i) {
if (!lst[s[i] - 'a']) {
f[i] = 2ll * f[i - 1] % p;
} else {
f[i] = (2ll * f[i - 1] % p - f[lst[s[i] - 'a'] - 1] + p) % p;
}
lst[s[i] - 'a'] = i;
}
ans = (ans + f[r_]) % p;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
std::cin >> s >> t;
revt = t;
std::reverse(revt.begin(), revt.end());
revt = "$" + revt;
s = "$" + s;
t = "$" + t;
init();
// std::cout << t << "\n" << revt << "\n";
int now = n + 1, flag = 1;
posrevt[0] = n + 1;
for (int i = 1; i <= m; ++ i) {
if (!to2[now][t[i] - 'a']) {
flag = 0;
break;
}
now = to2[now][t[i] - 'a'];
posrevt[i] = now;
}
now = 0;
post[0] = 0;
for (int i = 1; i <= m; ++ i) {
if (!to1[now][t[i] - 'a']) {
flag = 0;
break;
}
now = to1[now][t[i] - 'a'];
post[i] = now;
}
if (!flag) {
std::cout << 0 << "\n";
return 0;
}
for (int i = 0; i < m; ++ i) {
if (is_palindrome(i + 1, m) && posrevt[m] > post[i]) ++ ans;
}
// std::cout << ans << "\n";
calc(post[m] + 1, posrevt[m] - 1);
std::cout << ans << "\n";
return 0;
}
/*
14 7
caababaababaac
caababa
caababa ac
caababa baac
caababa babaac
caababa ababaac
16 7
caababacdababaac
caababa
11 7
caabababaac
caababa
*/
/*
10 2
abbbbababa
ab
*/
D
线段树,哈希。
原CF1418G,没有任何技术含量的改编。
考虑枚举区间右端点 \(r\),检查有哪些左端点 \(l\) 是合法的。
考虑前缀 \(1\sim r\) 中的所有权值 \(v\),记 \(v\) 最后 \(k+1\) 次出现位置为 \(p_{v, k+1}, p_{v, k}, \cdots, p_{v, 1}\),为了保证区间内要么恰好有 \(k\) 个 \(v\) 要么一个也没有,则显然合法的区间左端点仅可能位于 \((p_{v, k+1}, p_{v, k}] \cup (p_{v, 1}, r]\) 中。所有 \(v\) 的区间 \((p_{v, k+1}, p_{v, k}] \cup (p_{v, 1}, r]\) 取交即为合法左端点的数量。
上述问题显然可以用线段树维护。初始时所有位置均为 0,考虑在枚举 \(r\) 的同时动态维护每种权值的区间 \((p_{v, k+1}, p_{v, k}] \cup (p_{v, 1}, r]\),并将该区间之外的所有位置均+1。查询时仅需查询 \(1\sim i\) 中有多少个 0 即可。赋值后所有位置的权值均在 \([0, n]\) 中,0 一定是最小值,于是仅需线段树维护区间最小值与区间最小值的数量,查询时仅统计区间最小值为 0 的区间的贡献即可。
总时间复杂度 \(O(n\log n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k, a[kN];
int next[kN], before[kN], pk[kN], pk1[kN];
//=============================================================
namespace seg {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
const int kNode = kN << 2;
LL cntmin[kNode], mina[kNode], tag[kNode];
void pushup(int now_) {
cntmin[now_] = 0;
mina[now_] = std::min(mina[ls], mina[rs]);
if (mina[now_] == mina[ls]) cntmin[now_] += cntmin[ls];
if (mina[now_] == mina[rs]) cntmin[now_] += cntmin[rs];
}
void pushdown(int now_) {
if (!tag[now_]) return ;
mina[ls] += tag[now_], mina[rs] += tag[now_];
tag[ls] += tag[now_], tag[rs] += tag[now_];
tag[now_] = 0;
}
void build(int now_, int L_, int R_) {
tag[now_] = 0;
if (L_ == R_) {
mina[now_] = 0;
cntmin[now_] = 1;
return ;
}
build(ls, L_, mid), build(rs, mid + 1, R_);
pushup(now_);
}
int query(int now_, int L_, int R_, int l_, int r_) {
if (l_ <= L_ && R_ <= r_) {
if (mina[now_] == 0) return cntmin[now_];
return 0;
}
pushdown(now_);
int ret = 0;
if (l_ <= mid) ret += query(ls, L_, mid, l_, r_);
if (r_ > mid) ret += query(rs, mid + 1, R_, l_, r_);
return ret;
}
void modify(int now_, int L_, int R_, int l_, int r_, int k_) {
if (l_ > r_) return ;
if (l_ <= L_ && R_ <= r_) {
mina[now_] += k_;
tag[now_] += k_;
return ;
}
pushdown(now_);
if (l_ <= mid) modify(ls, L_, mid, l_, r_, k_);
if (r_ > mid) modify(rs, mid + 1, R_, l_, r_, k_);
pushup(now_);
}
}
void init() {
seg::build(1, 1, n);
std::map<int, int> lst;
for (int i = 0; i <= n + 5; ++ i) {
next[i] = before[i] = pk[i] = pk1[i] = 0;
}
for (int i = n; i; -- i) {
if (lst.count(a[i])) next[i] = lst[a[i]], before[lst[a[i]]] = i;
lst[a[i]] = i;
}
for (auto [v, p]: lst) {
int p1 = p, flag = 1;
for (int i = 1; i < k; ++ i) {
if (!next[p1]) {
flag = 0;
break;
}
p1 = next[p1];
}
if (!flag) continue;
pk[p1] = p;
while (next[p1]) {
pk1[next[p1]] = p;
p1 = next[p1], p = next[p];
pk[p1] = p;
}
}
}
//=============================================================
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 >> k;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
init();
LL ans = 0;
for (int i = 1; i <= n; ++ i) {
if (before[i]) {
seg::modify(1, 1, n, 1, pk1[before[i]], -1);
seg::modify(1, 1, n, pk[before[i]] + 1, before[i], -1);
}
seg::modify(1, 1, n, 1, pk1[i], 1);
seg::modify(1, 1, n, pk[i] + 1, i, 1);
ans += 1ll * seg::query(1, 1, n, 1, i);
}
std::cout << ans << "\n";
}
return 0;
}
此外还有一种小清新哈希做法,之后再补,可以看下原CF1418G的题解。
C
构造(?
用任意一种 \(O(\log^2 n)\) 的并行排序都能过,先不补了。
双调排序的做法详见官方题解。
H
呃呃大模拟,懒得补了。
G
网络流。
经典最小割对偶图上最短路。
写在最后
呃呃感觉屁也没学到。
你说的对但是今天是伟大的御阿礼之子第九代稗田阿求同志的三十华诞:

浙公网安备 33010602011771号