[题解]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

题意

  • 有一个只包含 JOI 的长度为 \(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\) 之后字符 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\) 个长度单位

  • 求最大收益

  • \(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;
}
posted @ 2020-06-21 11:49  epic01  阅读(362)  评论(0编辑  收藏  举报