Y3OI Summer Round #5 题解
前言
《关于出题人不知道比赛已经比完了这档事》
题解
以下是各题的题解。
Y3OI Summer Round #5 A. 完美的字符串
对于 \(10\%\) 的数据,签到送分。
对于 \(30\%\) 的数据,枚举 \(p_i\),再枚举 \(a_i\),判断可以用 KMP 算法,也可以不用,时间复杂度 \(O(n^2\log n)\)。
对于 \(100\%\) 的数据,考虑 KMP 算法中 \(f\) 数组的性质:对于每个 \(i\) 而言,长度为 \(f[i]\) 的前缀与长度为 \(f[i]\) 的后缀是相等的。
由题意可知,如果 \(i\) 有一个公共的前后缀,其长度为 \(l\),那么这个前缀 \(i\) 就有一个周期为 \(i-l\)。于是我们用上失配数组 \(f\) 求解即可。
但是,本题求的是最大的周期,因此我们应该求出最小的 \(l\) 满足题意。当然,这里可以用“记忆化”的思想来简化时间复杂度。
这样,时间复杂度为 \(O(n)\)。
#include <bits/stdc++.h>
#define Re register
using namespace std;
typedef long long ll;
const int N = 10000005;
char s[N];
int n, f[N];
ll ans;
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
for (Re int i = 2, j = 0; i <= n; i++) {
while (j > 0 && s[i] != s[j + 1]) j = f[j];
if (s[i] == s[j + 1])
j++;
f[i] = j;
}
for (Re int i = 1; i <= n; i++) {
int j = i;
while (f[j] > 0) j = f[j];
if (f[i] > 0)
f[i] = j;
ans += i - j;
}
printf("%lld", ans);
return 0;
}
Y3OI Summer Round #5 B. 收割庄稼
对于 \(20\%\) 的数据,暴力跑一遍即可。
对于 \(100\%\) 的数据,考虑上一棵线段树。(这应该挺好想的,不过为什么没有人写呢?)
我们发现,庄稼的高度始终是单调不减的。
也就是说,我们将生长速度 \(a_i\) 从小到大排个序,就可以得到庄稼的高度的关系:\(l_i\leq l_j,(i\leq j)\)。
于是对于每个询问,我们只要找到被割的庄稼的左端点,进行区间修改,用线段树维护区间和再减去修改后的区间和就可以了。
时间复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define Re register
using namespace std;
typedef long long ll;
const int N = 500005;
struct SegTree {
ll l, r, sum, mx, lzy, day;
} T[N << 2];
int n, m;
ll a[N], sum[N], d, b;
inline void build(int p, int l, int r) {
T[p].lzy = -1, T[p].l = l, T[p].r = r;
if (l == r)
return;
int mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
}
inline void pushup(int p) {
T[p].sum = T[p << 1].sum + T[p << 1 | 1].sum;
T[p].mx = T[p << 1 | 1].mx;
}
inline void update1(int p, int l, int r, ll val) {
T[p].day += val;
T[p].sum += 1ll * (sum[r] - sum[l - 1]) * val;
T[p].mx += 1ll * a[r] * val;
}
inline void update2(int p, int l, int r, ll val) {
T[p].lzy = T[p].mx = val;
T[p].sum = 1ll * (r - l + 1) * val;
T[p].day = 0;
}
inline void pushdown(int p) {
int mid = (T[p].l + T[p].r) >> 1, l = T[p].l, r = T[p].r;
if (T[p].lzy != -1) {
update2(p << 1, l, mid, T[p].lzy);
update2(p << 1 | 1, mid + 1, r, T[p].lzy);
T[p].lzy = -1;
}
if (T[p].day) {
update1(p << 1, l, mid, T[p].day);
update1(p << 1 | 1, mid + 1, r, T[p].day);
T[p].day = 0;
}
}
inline int query1(int p, ll v) {
if (T[p].sum < v)
return -1;
if (T[p].l == T[p].r)
return T[p].l;
pushdown(p);
if (T[p << 1].mx >= v)
return query1(p << 1, v);
return query1(p << 1 | 1, v);
}
inline ll query2(int p, int l, int r, ll v) {
ll ret = 0;
if (l > r)
return 0;
if (l <= T[p].l && T[p].r <= r) {
ll tmp = T[p].sum;
update2(p, T[p].l, T[p].r, v);
return tmp - T[p].sum;
}
int mid = (T[p].l + T[p].r) >> 1;
if (l <= mid)
ret += query2(p << 1, l, r, v);
if (mid < r)
ret += query2(p << 1 | 1, l, r, v);
pushup(p);
return ret;
}
int main() {
scanf("%d%d", &n, &m);
for (Re int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
sort(a + 1, a + n + 1);
for (Re int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
build(1, 1, n);
ll lst = 0;
while (m--) {
scanf("%lld%lld", &d, &b);
update1(1, 1, n, d - lst);
lst = d;
ll L = query1(1, b);
if (L == -1)
printf("0\n");
else
printf("%lld\n", query2(1, L, n, b));
}
return 0;
}
Y3OI Summer Round #5 C. 刷题
对于 \(5\%\) 的数据,直接 dfs 即可。
对于 \(15\%\) 的数据,本来是给 \(30\%\) 那一档的大常数选手分,结果有些人玄学做法搞过了(
对于 \(30\%\) 的数据,考虑网络流板子套上,建边如下:
-
\(S\to i,f=a_i\);
-
\(i\to T',f=b_i\);
-
\(i\to i+1,f=∞\);
-
\(T'\to T,f=k\)。
时间复杂度 \(O(n^3)\),常数好可以过。
对于另外 \(20\%\) 的数据,没啥用,就放那,算是个比较整齐的数据(
对于另外 \(10\%\) 的数据,显然 \(a\) 取前 \(k\) 个,\(b\) 取后 \(k\) 个。
对于 \(100\%\) 的数据,用 wqs 二分或线段树模拟费用流的过程即可。(因为本题费用流模型并不复杂)
时间复杂度 \(O(n\log n)\) 或 \(O(n\log^2 n)\)。
#include <bits/stdc++.h>
#define Re register
using namespace std;
typedef long long ll;
const int N = 500005;
int n, k;
ll a[N], b[N];
priority_queue<pair<ll, ll> > q;
int main() {
scanf("%d%d", &n, &k);
for (Re int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (Re int i = 1; i <= n; i++) {
scanf("%lld", &b[i]);
}
ll l = 0, r = 2000000009;
while (l <= r) {
ll mid = (l + r) >> 1;
int cnt = 0;
ll tot = 0;
for (Re int i = 1; i <= n; i++) {
q.push(make_pair(mid - a[i], 0));
ll val = b[i] - q.top().first;
if (val < 0) {
tot += val;
q.pop();
q.push(make_pair(b[i], 1));
}
}
while (!q.empty()) cnt += q.top().second, q.pop();
if (cnt == k) {
printf("%lld", tot + 1ll * k * mid);
return 0;
}
if (cnt < k)
l = mid + 1;
if (cnt > k)
r = mid - 1;
}
return 0;
}
Y3OI Summer Round #5 D. 最短路
对于 \(10\%\) 的数据,随便跑即可。
对于 \(40\%\) 的数据,考虑 Dijkstra 算法,发现每个点可以更新的点的最短路都是 \(dis[i]+a[i]\)。
故维护一个堆,按这个排序,可以保证每个节点只被更新一次。
时间复杂度 \(O(n^2\log n)\)。
对于另外 \(20\%\) 的数据,就是一个裸的 Dijkstra 算法。
对于 \(100\%\) 的数据,考虑如何快速找出所有没有更新的点。
用点分树!
从每个重心开始 bfs,然后用队列记录下所遍历到的每个点,这显然总共只有 \(n\log n\) 个节点。
查找没修改过的点,对于子树内的点,可以直接删队列内的点,对于子树外的点,可以找点分树父亲的队列进行修改。
时间复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define Re register
using namespace std;
typedef long long ll;
const int N = 200005;
struct Edge {
int ver, nxt;
} e[N << 1];
int n, S, cnt, T;
int hd[N], d[N], dfn[N], pos[N], dep[N];
bool vis[N];
int lg[N << 1], st[N << 1][21];
ll dis[N], a[N];
struct cmp {
bool operator()(int x, int y) const { return dis[x] + a[x] > dis[y] + a[y]; }
};
priority_queue<int, vector<int>, cmp> q;
inline void add(int u, int v) {
cnt++;
e[cnt] = (Edge){ v, hd[u] };
hd[u] = cnt;
}
inline void dfs(int u, int f) {
dep[u] = dep[f] + 1;
pos[u] = ++T;
st[T][0] = u;
for (Re int i = hd[u]; i; i = e[i].nxt) {
int v = e[i].ver;
if (v == f)
continue;
dfs(v, u);
st[++T][0] = u;
}
}
inline int lca(int x, int y) {
x = pos[x], y = pos[y];
if (x > y)
swap(x, y);
int t = lg[y - x + 1];
return dep[st[x][t]] < dep[st[y - (1 << t) + 1][t]] ? st[x][t] : st[y - (1 << t) + 1][t];
}
inline int Dis(int x, int y) { return dep[x] + dep[y] - dep[lca(x, y)] * 2; }
int sz, rt, mx;
int sze[N], F[N], mrk[N], in[N];
inline void getroot(int x, int f) {
sze[x] = 1;
int son = 0;
for (Re int i = hd[x]; i; i = e[i].nxt) {
int v = e[i].ver;
if (v == f || vis[v])
continue;
getroot(v, x);
sze[x] += sze[v];
son = max(son, sze[v]);
}
son = max(son, sz - sze[x]);
if (son < mx)
mx = son, rt = x;
}
queue<pair<int, int> > Q;
int h[N], nxt[N * 30];
int Cnt, tp;
pair<int, int> son[N * 30], s[N];
inline void Add(int u, pair<int, int> now) {
Cnt++;
son[Cnt] = now;
nxt[Cnt] = h[u];
h[u] = Cnt;
}
inline void bfs(int x) {
Q.push(make_pair(0, x));
in[x] = x;
while (!Q.empty()) {
pair<int, int> now = Q.front();
Q.pop();
s[++tp] = now;
int u = now.second, dep = now.first;
for (Re int i = hd[u]; i; i = e[i].nxt) {
int v = e[i].ver;
if (in[v] == x || vis[v])
continue;
in[v] = x;
Q.push(make_pair(dep + 1, v));
}
}
while (tp) Add(x, s[tp--]);
}
inline void div(int x) {
vis[x] = 1;
for (Re int i = hd[x]; i; i = e[i].nxt)
if (!vis[e[i].ver])
bfs(x);
for (Re int i = hd[x]; i; i = e[i].nxt) {
int v = e[i].ver;
if (vis[v])
continue;
sz = sze[v];
mx = 1e9;
getroot(v, x);
F[rt] = x;
div(rt);
}
}
inline void del(int x, int dit, ll v, int u) {
if (d < 0)
return;
while (h[x] && son[h[x]].first <= dit) {
int k = son[h[x]].second;
h[x] = nxt[h[x]];
if (mrk[k])
continue;
dis[k] = v, mrk[k] = 1, q.push(k);
}
if (F[x])
del(F[x], d[u] - Dis(u, F[x]), v, u);
}
int main() {
scanf("%d", &n);
for (Re int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
for (Re int i = 1; i <= n; i++) {
scanf("%d", &d[i]);
}
for (Re int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
dfs(1, 0);
for (Re int i = 2; i <= T; i++) lg[i] = lg[i >> 1] + 1;
for (Re int i = 1; i <= 19; i++)
for (Re int j = 1; j + (1 << i) <= T; j++)
st[j][i] = dep[st[j][i - 1]] < dep[st[j + (1 << i - 1)][i - 1]] ? st[j][i - 1]
: st[j + (1 << i - 1)][i - 1];
sz = n;
mx = 1e9;
getroot(1, 0);
div(rt);
dis[1] = 0;
mrk[1] = 1;
q.push(1);
while (!q.empty()) {
int u = q.top();
q.pop();
del(u, d[u], dis[u] + a[u], u);
}
for (Re int i = 1; i <= n; i++) printf("%lld ", dis[i]);
return 0;
}