[题解]JOI 2020 Final 题解

  • 省选临近却无比颓废,逼自己去做 JOI

A

题意

  • 给定一个长度为 (N+1) 的数组 (A) 和一个长度为 (N) 的数组 (B)

  • 对于每个 (1le ile N+1),求出:对每个 (1le jle N) 求出一个 (k_j),满足 (k) 数组包含 (1)(N+1) 中除 (i) 外的所有数,最小化 (max_{i=1}^Nmax(A_{k_i}-B_i,0))

  • (1le Nle 2 imes10^5)(1le A_i,B_ile10^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(Nlog 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

题意

  • 有一个只包含 JOI 的长度为 (N) 的字符串

  • 每次可以删掉一个字符,但如果删掉的字符不在串首或串尾,则要花费 (1) 的代价

  • 求把原串变成 JJ... JOO...OII... I(三种字母均有 (K) 个)的最小代价,或者判断无解

  • (3le Nle2 imes10^5)(1le Klefrac N3)

Solution

  • 易得选出一个最短的子串包含 JJ... JOO... OII... I 为子序列,这个子串长度减去 (3K) 即为答案

  • 于是我们只需求出对于每个 (i),以 (i) 开头的后缀中该子序列最后一个字符所在的下标

  • 预处理 (i) 之后字符 JOI 分别第 (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) 个长度单位

  • 求最大收益

  • (1le Nle 200)(1le L,T_ile10^9)(1le 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) 的最短路的最小值

  • (2le Nle 200)(1le Mle 5 imes10^4)(0le Cle10^6)(0le Dle 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),回答对于所有 (Lle ile R)(S) 的区间 ([max(1,i-T),i]) 最大值之和

  • (1le N,Qle2 imes10^5)(1le Sle10^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) 为结尾的区间最大值的条件:

  • [iin[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}^Rsum_k[l_kle ile r_k]S_k ]

  • 考虑容斥,用 (ile 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
", ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/xyz32768/p/13171680.html