[题解]JOI 2020 Final 题解
省选临近却无比颓废,逼自己去做 JOI
A
题意
-
给定一个长度为 \(N+1\) 的数组 \(A\) 和一个长度为 \(N\) 的数组 \(B\)
-
对于每个 \(1\le i\le N+1\),求出:对每个 \(1\le j\le N\) 求出一个 \(k_j\),满足 \(k\) 数组包含 \(1\) 到 \(N+1\) 中除 \(i\) 外的所有数,最小化 \(\max_{i=1}^N\max(A_{k_i}-B_i,0)\)
-
\(1\le N\le 2\times10^5\),\(1\le A_i,B_i\le10^9\)
Solution
-
可以发现 \(a<b,c<d\) 时 \(b-c>a-c\) 且 \(b-c>b-d\),故 \(a\) 配 \(c\),\(b\) 配 \(d\) 比 \(a\) 配 \(d\),\(b\) 配 \(c\) 优
-
故把两个数组排序,预处理出排序以后两个数组长度为 \(i\) 的前缀匹配的 \(\max(a-b)\),后缀同理,即可求出答案
-
\(O(N\log N)\)
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
typedef long long ll;
const int N = 2e5 + 5;
int n, b[N];
ll pre[N], suf[N], ans[N];
struct elem
{
int x, id;
} a[N];
inline bool comp(elem a, elem b)
{
return a.x < b.x;
}
int main()
{
read(n);
for (int i = 1; i <= n + 1; i++) read(a[i].x), a[i].id = i;
std::sort(a + 1, a + n + 2, comp);
for (int i = 1; i <= n; i++) read(b[i]);
std::sort(b + 1, b + n + 1);
for (int i = 1; i <= n + 1; i++) pre[i] = Max(pre[i - 1], 1ll * a[i].x - b[i]);
for (int i = n + 1; i >= 1; i--) suf[i] = Max(suf[i + 1], 1ll * a[i].x - b[i - 1]);
for (int i = 1; i <= n + 1; i++) ans[a[i].id] = Max(pre[i - 1], suf[i + 1]);
for (int i = 1; i <= n + 1; i++) printf("%lld ", ans[i]);
return puts(""), 0;
}
B
题意
-
有一个只包含
J
、O
、I
的长度为 \(N\) 的字符串 -
每次可以删掉一个字符,但如果删掉的字符不在串首或串尾,则要花费 \(1\) 的代价
-
求把原串变成
JJ... JOO...OII... I
(三种字母均有 \(K\) 个)的最小代价,或者判断无解 -
\(3\le N\le2\times10^5\),\(1\le K\le\frac N3\)
Solution
-
易得选出一个最短的子串包含
JJ... JOO... OII... I
为子序列,这个子串长度减去 \(3K\) 即为答案 -
于是我们只需求出对于每个 \(i\),以 \(i\) 开头的后缀中该子序列最后一个字符所在的下标
-
预处理 \(i\) 之后字符
J
、O
、I
分别第 \(K\) 次出现的位置即可 -
\(O(N)\)
Code
#include <bits/stdc++.h>
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
int n, k, nxt[N][3], top[3], stk[3][N], ans = INF;
char s[N];
int main()
{
std::cin >> n >> k;
scanf("%s", s + 1);
for (int i = n; i >= 1; i--)
{
int c = s[i] == 'I' ? 2 : (s[i] == 'O' ? 1 : 0);
stk[c][++top[c]] = i;
for (int c = 0; c < 3; c++)
if (top[c] >= k) nxt[i][c] = stk[c][top[c] - k + 1] + 1;
}
for (int i = 1; i <= n; i++)
{
int lst = nxt[nxt[nxt[i][0]][1]][2];
if (lst) ans = Min(ans, lst - i - k * 3);
}
return std::cout << (ans == INF ? -1 : ans) << std::endl, 0;
}
C
题意
-
有一个长为 \(L\) 的环,环上有 \(N\) 个点,第 \(i\) 个位置为 \(X_i\),两两不同且不为原点
-
如果一个点在时刻 \(T_i\) 之前经过则会得到 \(1\) 的收益
-
一开始在原点,每次可以花费 \(1\) 的时间向某个方向走 \(1\) 个长度单位
-
求最大收益
-
\(1\le N\le 200\),\(1\le L,T_i\le10^9\),\(1\le X_i<L\),\(X\) 按严格递增序给出
Solution
-
显然每次走到的点一定是一段前缀并上一段后缀
-
DP:\(f_{l,r,i}\) 和 \(g_{l,r,i}\) 分别表示走到了 \([1,l]\cup[r,n]\),获得 \(i\) 收益需要的最少时间,\(f\) 表示最后停在 \(l\),\(g\) 表示最后停在 \(r\)
-
转移即枚举下一次到达 \(l+1\) 还是 \(r-1\),具体方程略
-
\(O(N^3)\)
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline void GMin(T &a, const T &b) {if (b < a) a = b;}
template <class T>
inline void GMax(T &a, const T &b) {if (b > a) a = b;}
const int N = 205, INF = 0x3f3f3f3f;
int n, L, X[N], T[N], f[N][N][N], g[N][N][N], ans;
int main()
{
read(n); read(L); X[n + 1] = L;
for (int i = 1; i <= n; i++) read(X[i]);
for (int i = 1; i <= n; i++) read(T[i]);
memset(f, INF, sizeof(f)); memset(g, INF, sizeof(g));
f[0][n + 1][0] = 0; g[0][n + 1][0] = 0;
for (int l = 0; l <= n; l++)
for (int r = n + 1; r > l + 1; r--)
for (int i = 0; i <= l + n - r + 1; i++)
{
int tf = f[l][r][i] + X[l + 1] - X[l],
tg = g[l][r][i] + X[r] - X[r - 1],
wf = g[l][r][i] + L - X[r] + X[l + 1],
wg = f[l][r][i] + L - X[r - 1] + X[l];
GMin(f[l + 1][r][i + (tf <= T[l + 1])], tf);
GMin(g[l][r - 1][i + (tg <= T[r - 1])], tg);
GMin(f[l + 1][r][i + (wf <= T[l + 1])], wf);
GMin(g[l][r - 1][i + (wg <= T[r - 1])], wg);
}
for (int l = 0; l <= n; l++)
for (int r = n + 1; r > l; r--)
for (int i = 0; i <= l + n - r + 1; i++)
{
if (l >= 1 && f[l][r][i] <= T[l]) GMax(ans, i);
if (r <= n && g[l][r][i] <= T[r]) GMax(ans, i);
}
return std::cout << ans << std::endl, 0;
}
D
题意
-
给定一个 \(N\) 点 \(M\) 边的有向图,每条边有长度 \(C\) 和翻转代价 \(D\),求翻转最多一条边之后,\(1\) 到 \(N\) 的最短路加上 \(N\) 到 \(1\) 的最短路的最小值
-
\(2\le N\le 200\),\(1\le M\le 5\times10^4\),\(0\le C\le10^6\),\(0\le D\le 10^9\),可能存在重边
Solution
-
考虑翻转 \(<u,v>\) 之后 \(1\) 到 \(N\) 最短路的可能情况:
-
(1)\(1\) 先到 \(v\)(不能经过 \(u\)),然后走 \(<v,u>\),最后 \(u\) 到 \(N\)(不能经过 \(v\))
-
(2)\(1\) 到 \(u\),然后通过该边的一条重边到达 \(v\),最后 \(v\) 到 \(N\)
-
(3)\(1\) 到 \(N\),不经过点 \(u\)
-
(4)\(1\) 到 \(N\),不经过点 \(v\)
-
(5)\(1\) 到 \(N\),\(v\) 在 \(u\) 前,也就是说先 \(1\) 到 \(u\),再 \(u\) 到 \(N\)(不能经过 \(v\))
-
(6)\(1\) 到 \(N\),\(u\) 和 \(v\) 之间隔了至少一个点,也就是枚举 \(x\),先 \(1\) 到 \(x\)(不经过 \(v\)),再 \(x\) 到 \(N\)(不经过 \(u\))
-
\(N\) 到 \(1\) 同理
-
于是只需对于任意 \(u\) 预处理不经过点 \(u\) 的单源最短路,\(N\) 次
Dijkstra
即可,注意可以使用不加优先队列的 \(O(N^2+M)\) 暴力Dijkstra
-
\(O(N^3+NM)\)
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 205, M = 5e4 + 5, INF = 0x3f3f3f3f;
int n, m, d[M], ans, d1[N][N], d2[N][N], d3[N][N], d4[N][N], alt[M], lf[M], rf[M];
bool vis[N];
std::vector<int> eg[N][N];
struct graph
{
int ecnt, nxt[M], adj[N], st[M], go[M], val[M];
void add_edge(int u, int v, int w)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v; val[ecnt] = w;
}
void dij(int S, int X, int *dis)
{
for (int i = 1; i <= n; i++) dis[i] = INF;
memset(vis, 0, sizeof(vis));
if (S != X) dis[S] = 0;
while (1)
{
int mn = INF, u = 0;
for (int v = 1; v <= n; v++)
if (v != X && !vis[v] && dis[v] < mn) mn = dis[v], u = v;
if (!u) return; vis[u] = 1;
for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
if (v != X) dis[v] = Min(dis[v], dis[u] + val[e]);
}
}
} G1, G2;
int main()
{
int x, y, z;
read(n); read(m);
for (int i = 1; i <= m; i++)
read(x), read(y), read(z), read(d[i]), G1.add_edge(x, y, z),
G2.add_edge(y, x, z), eg[x][y].push_back(i);
for (int i = 0; i <= n; i++) G1.dij(1, i, d1[i]), G1.dij(n, i, d2[i]),
G2.dij(1, i, d3[i]), G2.dij(n, i, d4[i]);
ans = d1[0][n] + d2[0][1];
for (int u = 1; u <= n; u++)
for (int v = 1; v <= n; v++)
{
int len = eg[u][v].size();
lf[0] = rf[len + 1] = INF;
for (int i = 1; i <= len; i++)
lf[i] = Min(lf[i - 1], G1.val[eg[u][v][i - 1]]);
for (int i = len; i >= 1; i--)
rf[i] = Min(rf[i + 1], G1.val[eg[u][v][i - 1]]);
for (int i = 1; i <= len; i++) alt[eg[u][v][i - 1]] =
Min(lf[i - 1], rf[i + 1]);
}
for (int e = 1; e <= m; e++)
{
int u = G1.st[e], v = G1.go[e], dis1 = Min(INF, d1[u][v] + d4[v][u] + G1.val[e]),
dis2 = Min(INF, d2[u][v] + d3[v][u] + G1.val[e]);
if (alt[e] < INF) dis1 = Min(dis1, d1[0][u] + d4[0][v] + alt[e]),
dis2 = Min(dis2, d2[0][u] + d3[0][v] + alt[e]);
dis1 = Min(dis1, Min(d1[u][n], d1[v][n]));
dis2 = Min(dis2, Min(d2[u][1], d2[v][1]));
dis1 = Min(dis1, d1[0][u] + d4[v][u]);
dis2 = Min(dis2, d2[0][u] + d3[v][u]);
for (int x = 1; x <= n; x++) if (x != u && x != v)
dis1 = Min(dis1, d1[v][x] + d4[u][x]),
dis2 = Min(dis2, d2[v][x] + d3[u][x]);
if (dis1 < INF && dis2 < INF) ans = Min(ans, dis1 + dis2 + d[e]);
}
return std::cout << (ans < INF ? ans : -1) << std::endl, 0;
}
E
题意
-
一个 \(N\) 个数的序列 \(S\),\(Q\) 组询问
-
每次询问给出 \(T,L,R\),回答对于所有 \(L\le i\le R\),\(S\) 的区间 \([\max(1,i-T),i]\) 最大值之和
-
\(1\le N,Q\le2\times10^5\),\(1\le S\le10^9\)
Solution
-
为了方便讨论,下面 \(T\) 加上一,即表示以 \(i\) 为结尾的长度为 \(K\) 的区间
-
先预处理 \(pre_i\) 表示 \(i\) 左边第一个严格大于 \(a_i\) 的数所在下标(没有则为 \(-\infty\))(由于左端点是 \(\max(1,i-T+1)\),故不存在时不能为 \(0\))
-
\(suf_i\) 表示 \(i\) 右边第一个不小于 \(a_i\) 的数所在下标(没有则为 \(N+1\))(注意一个是严格大于一个是不小于,为避免重复)
-
考虑固定一个 \(T\) 时,\(S_k\) 成为以 \(i\) 为结尾的区间最大值的条件:
-
\[i\in[k+\max(0,T-k+pre_k),\min(suf_k-1,k+T-1)] \]
-
设这个区间为 \([l_k,r_k]\),考虑这两个端点随 \(T\) 递增时的变化:\(r\) 先递增 \(1\),然后维持不变,\(l\) 先维持不变,然后递增 \(1\)
-
回到询问,我们要求的是:
-
\[\sum_{i=L}^R\sum_k[l_k\le i\le r_k]S_k \]
-
考虑容斥,用 \(i\le r\) 减去 \(i<l\),下面只考虑 \(r\)
-
如果在当前的 \(T\) 下 \(r_k\) 维持不变,则可以用线段树,区间 \([\le r_k]\) 加一,询问区间 \([L,R]\) 和
-
否则把 \(r_k\) 表示成 \(r'_k+T\) 的形式,再开一棵线段树,区间 \([\le r'_k]\) 加一,询问区间 \([L-T,R-T]\) 和
-
而 \(T\) 是不断变化的,故可以把询问离线按 \(T\) 排序之后,边询问边按 \(T\) 从小到大处理 \(l\) 和 \(r\) 的变化,注意 \(l\) 不能大于 \(r\),遇到这种情况需要把对应区间删掉
-
\(O((N+Q)\log N)\)
Code
#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 2e5 + 5, M = N << 3;
int n, q, a[N], top, stk[N], pre[N], suf[N];
ll ans[N];
struct seg
{
ll sum[M], add[M];
void down(int l, int r, int mid, int p)
{
add[p2] += add[p]; add[p3] += add[p];
sum[p2] += add[p] * (mid - l + 1); sum[p3] += add[p] * (r - mid);
add[p] = 0;
}
void change(int l, int r, int s, int e, ll v, int p)
{
if (e < l || s > r) return;
if (s <= l && r <= e) return (void) (add[p] += v, sum[p] += v * (r - l + 1));
int mid = l + r >> 1; down(l, r, mid, p);
change(l, mid, s, e, v, p2); change(mid + 1, r, s, e, v, p3);
sum[p] = sum[p2] + sum[p3];
}
ll ask(int l, int r, int s, int e, int p)
{
if (e < l || s > r) return 0;
if (s <= l && r <= e) return sum[p];
int mid = l + r >> 1; down(l, r, mid, p);
return ask(l, mid, s, e, p2) + ask(mid + 1, r, s, e, p3);
}
} L1, L2, R1, R2;
struct query
{
int l, r, id;
};
std::vector<query> que[N];
std::vector<int> t1[N], t2[N], t3[N];
int main()
{
int t, l, r;
read(n); read(q);
for (int i = 1; i <= n; i++) read(a[i]);
stk[top = 0] = 0;
for (int i = 1; i <= n; i++)
{
while (top && a[stk[top]] <= a[i]) top--;
pre[i] = stk[top]; stk[++top] = i;
}
stk[top = 0] = n + 1;
for (int i = n; i >= 1; i--)
{
while (top && a[stk[top]] < a[i]) top--;
suf[i] = stk[top]; stk[++top] = i;
}
for (int i = 1; i <= q; i++)
{
read(t); read(l); read(r); if (t < n) t++;
que[t].push_back((query) {l, r, i});
}
for (int i = 1; i <= n; i++)
L1.change(-n, n, -n, i, a[i], 1), R2.change(-n, n, -n, i - 1, a[i], 1);
for (int i = 1; i <= n; i++)
{
t1[suf[i] - i].push_back(i);
if (pre[i]) t2[i - pre[i]].push_back(i), t3[suf[i] - pre[i]].push_back(i);
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < t1[i].size(); j++)
R2.change(-n, n, -n, t1[i][j] - 1, -a[t1[i][j]], 1),
R1.change(-n, n, -n, suf[t1[i][j]] - 1, a[t1[i][j]], 1);
for (int j = 0; j < t2[i].size(); j++)
L1.change(-n, n, -n, t2[i][j], -a[t2[i][j]], 1),
L2.change(-n, n, -n, t2[i][j] - i, a[t2[i][j]], 1);
for (int j = 0; j < t3[i].size(); j++)
R1.change(-n, n, -n, suf[t3[i][j]] - 1, -a[t3[i][j]], 1),
L2.change(-n, n, -n, suf[t3[i][j]] - i, -a[t3[i][j]], 1);
for (int j = 0; j < que[i].size(); j++)
{
int l = que[i][j].l, r = que[i][j].r;
ans[que[i][j].id] = R1.ask(-n, n, l, r, 1) + R2.ask(-n, n, l - i, r - i, 1)
- L1.ask(-n, n, l + 1, r + 1, 1)
- L2.ask(-n, n, l - i + 1, r - i + 1, 1);
}
}
for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
return 0;
}