← 全部文章

JOI Final 2020 题解

本文目录(5 节)
  1. 長いだけのネクタイ (Just Long Neckties)
  2. JJOOII 2 (JJOOII 2)
  3. スタンプラリー 3 (Collecting Stamps 3)
  4. オリンピックバス (Olympic Bus)
  5. 火事 (Fire)

JOI Final 2020

長いだけのネクタイ (Just Long Neckties)

显然 a,ba,b 排序后对应位置上的数相减最优,multiset 维护一下即可,时间复杂度 O(nlog⁡n)\mathcal O(n \log n)。

const int N = 2e5 + 7;
int n, a[N], b[N], p[N], ans[N];
multiset<int> s;

int main() {
	rd(n), rda(a, n + 1), rda(b, n);
	for (int i = 1; i <= n + 1; i++) p[i] = i;
	sort(p + 1, p + n + 2, [&](int i, int j) { return a[i] < a[j]; });
	sort(b + 1, b + n + 1);
	for (int i = 1; i <= n; i++) s.insert(max(a[p[i+1]] - b[i], 0));
	ans[p[1]] = *--s.end();
	for (int i = 1; i <= n; i++) {
		s.erase(s.find(max(a[p[i+1]] - b[i], 0)));
		s.insert(max(a[p[i]] - b[i], 0));
		ans[p[i+1]] = *--s.end();
	}
	for (int i = 1; i <= n + 1; i++)
		print(ans[i], " \n"[i==n+1]);
	return 0;
}

JJOOII 2 (JJOOII 2)

前缀和一下,然后枚举开头 ss,二分出从 ss 开始的第 kk 个 J 的位置 xx,从 xx 开始的第 kk 个 O 的位置 yy,从 yy 开始的第 kk 个 I 的位置 zz,从而求得答案,时间复杂度 O(nlog⁡n)\mathcal O(n \log n)。

当然也可以用类似 Two Pointers 的思想做到线性。

const int N = 2e5 + 7, inf = 1e9;
int n, k, f[3][N], ans = inf;
char s[N];

int main() {
	rd(n, k), rds(s, n);
	map<char, int> p;
	p['J'] = 0, p['O'] = 1, p['I'] = 2;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < 3; j++) f[j][i] = f[j][i-1];
		++f[p[s[i]]][i];
	}
	for (int i = 0; i < n; i++) {
		if (f[0][i] + k > f[0][n]) break;
		int x = lower_bound(f[0] + 1, f[0] + n + 1, f[0][i] + k) - f[0];
		if (f[1][x] + k > f[1][n]) break;
		int y = lower_bound(f[1] + 1, f[1] + n + 1, f[1][x] + k) - f[1];
		if (f[2][y] + k > f[2][n]) break;
		int z = lower_bound(f[2] + 1, f[2] + n + 1, f[2][y] + k) - f[2];
		ans = min(ans, z - i - 3 * k);
	}
	print(ans == inf ? -1 : ans);
	return 0;
}

スタンプラリー 3 (Collecting Stamps 3)

断环成链,区间 DP。

设 fl,r,0/1,if_{l,r,0/1,i} 表示已经处理了 l∼rl\sim r 种邮票,现在在 l/rl/r 的位置上,已经收集了 ii 种邮票的最小花费时间。

每次枚举走到 l−1l-1 还是走到 r+1r+1,时间复杂度 O(n3)\mathcal O(n^3)。

const int N = 207;
const ll inf = 1e18;
int n, m, p[N<<1|1], t[N<<1|1], ans;
ll f[N<<1|1][N<<1|1][2][N];

inline void upd(ll &x, ll y) {
	x = min(x, y);
}

int main() {
	rd(n, m), rda(p + n + 1, n), rda(t + n + 1, n);
	for (int i = 1; i <= n; i++) p[i] = p[i+n+1] - m, t[i] = t[i+n+1];
	for (int l = 1; l <= n + 1; l++)
		for (int r = n + 1; r <= (n << 1 | 1); r++)
			for (int k = 0; k < 2; k++)
				for (int i = 0; i <= n; i++)
					f[l][r][k][i] = inf;
	f[n+1][n+1][0][0] = f[n+1][n+1][1][0] = 0;
	for (int len = 0; len < n; len++)
		for (int l = n + 1 - len, r = n + 1; l <= n + 1; l++, r++)
			for (int k = 0; k < 2; k++)
				for (int i = 0; i < n; i++)
					upd(f[l-1][r][0][i+(f[l][r][k][i]+p[k?r:l]-p[l-1]<=t[l-1])],
						f[l][r][k][i] + p[k?r:l] - p[l-1]),
					upd(f[l][r+1][1][i+(f[l][r][k][i]+p[r+1]-p[k?r:l]<=t[r+1])],
						f[l][r][k][i] + p[r+1] - p[k?r:l]);
	for (int l = 1; l <= n + 1; l++)
		for (int r = n + 1; r <= (n << 1 | 1); r++)
			for (int k = 0; k < 2; k++)
				for (int i = 0; i <= n; i++)
					if (f[l][r][k][i] != inf) ans = max(ans, i);
	print(ans);
	return 0;
}

オリンピックバス (Olympic Bus)

首先求出不变向时的答案,由于本题中的图是稠密图,我们考虑用 O(n2+m)\mathcal O(n^2 + m) 的 Dijkstra 跑。

考虑一条边 (u,v)(u,v) 变向之后造成的影响,不妨考虑 1→n1 \to n 的部分,n→1n \to 1 同理。

如果 (u,v)(u,v) 不在原本 1→n1 \to n 的最短路上,则此时的最短路要么不变,要么是强制经过 (v,u)(v,u) 且不经过 (u,v)(u,v) 的最短路。

考虑如何求后者,相当于我们要求不经过 (u,v)(u,v) 的情况下 1→v1 \to v 和 u→nu \to n 的最短路。考虑图中以 11 为起点的最短路树和反图中以 nn 为起点的最短路树,若 (u,v)(u,v) 不在前/后者中,则可以直接得到答案,否则重新跑一遍最短路。由于最短路树上的边数只有 O(n)\mathcal O(n) 条,因此这部分的时间复杂度为 O(n3+nm)\mathcal O(n^3 + nm)。

如果 (u,v)(u,v) 在原本 1→n1 \to n 的最短路上,则同样重新跑一遍最短路,时间复杂度同样为 O(n3+nm)\mathcal O(n^3 + nm)。

const int N = 207, M = 1e5 + 7;
const ll inf = 1e18;
int n, m;
int h[N], e[M], t[M], tot = 1;
ll c[M], d[M], now, ans = inf;
bool u[M];
struct Gragh {
	ll f[N];
	bool in[M];
} G1, Gn, GT1, GTn;

inline void add(int x, int y, int c, int d, int o = 1) {
	e[++tot] = y, t[tot] = h[x], h[x] = tot;
	::c[tot] = c, ::d[tot] = d, u[tot] = o;
	if (o) add(y, x, c, d, 0);
}

namespace Dij {
	ll e[N][N], f[N];
	int a[N][N], g[N];
	bool v[N];
	
	inline ll dij(int S, int T) {
		for (int x = 1; x <= n; x++) {
			for (int y = 1; y <= n; y++)
				e[x][y] = inf;
			for (int i = h[x]; i; i = t[i]) {
				if (!u[i]) continue;
				int y = ::e[i];
				if (e[x][y] > c[i])
					e[x][y] = c[i], a[x][y] = i;
			}
		}
		for (int i = 0; i <= n; i++)
			f[i] = inf, g[i] = v[i] = 0;
		f[S] = 0;
		for (int i = 1; i <= n; i++) {
			int x = 0;
			for (int j = 1; j <= n; j++) {
				if (v[j]) continue;
				if (f[j] < f[x]) x = j;
			}
			v[x] = 1;
			for (int y = 1; y <= n; y++) {
				if (v[y]) continue;
				if (f[y] > f[x] + e[x][y])
					f[y] = f[x] + e[x][y], g[y] = a[x][y];
			}
		}
		return f[T];
	}
	
	inline void get(Gragh &G) {
		for (int i = 1; i <= n; i++) G.f[i] = f[i], G.in[g[i]] = 1;
	}
}
using Dij::dij;
using Dij::get;

inline ll work(int i) {
	now = d[i];
	u[i] = 0, u[i^1] = 1;
	if (G1.in[i]) now += dij(1, n);
	else {
		ll cur = c[i] + G1.f[e[i]];
		if (GTn.in[i]) cur += dij(e[i^1], n);
		else cur += GTn.f[e[i^1]];
		now += min(G1.f[n], cur);
	}
	if (Gn.in[i]) now += dij(n, 1);
	else {
		ll cur = c[i] + Gn.f[e[i]];
		if (GT1.in[i]) cur += dij(e[i^1], 1);
		else cur += GT1.f[e[i^1]];
		now += min(Gn.f[1], cur);
	}
	u[i] = 1, u[i^1] = 0;
	return now;
}

int main() {
	rd(n, m);
	for (int i = 1, x, y, c, d; i <= m; i++)
		rd(x, y), rd(c, d), add(x, y, c, d);
	now += dij(1, n), get(G1);
	now += dij(n, 1), get(Gn);
	ans = min(ans, now);
	for (int i = 2; i <= tot; i++) u[i] ^= 1;
	dij(1, n), get(GT1);
	dij(n, 1), get(GTn);
	for (int i = 2; i <= tot; i++) u[i] ^= 1;
	for (int i = 2; i <= tot; i += 2) ans = min(ans, work(i));
	print(ans == inf ? -1 : ans);
	return 0;
}

火事 (Fire)

考虑平面直角坐标系中 ([1,n],[0,n])([1,n],[0,n]) 的部分,其中 (i,j)(i,j) 对应题目中的 Si(j)S_i(j)。

注意到每个 SiS_i 的覆盖范围都是一个平行四边形,而这样一个平行四边形可以拆成四个三角形,每个三角形 (a,b,c)(a,b,c) 表示 ([a,+∞),[x−b,+∞))([a,+\infty), [x-b,+\infty)) 的部分权值 +c+c。

再看询问,一次询问 (t,l,r)(t,l,r) 可以差分为 (t,r)−(t,l−1)(t,r) - (t,l-1),其中 (t,p)(t,p) 表示 ((−∞,p],t)((-\infty, p], t) 的权值和。

于是问题变成 (a,b,c)(a,b,c) 加 (t,p)(t,p) 求和,以 yy 轴正方向进行扫描线,考虑一个 (a,b,c)(a,b,c) 在满足 a−b≤ta-b \le t 的情况下对 (t,p)(t,p) 的贡献为 c⋅(min⁡(t+b,p)−min⁡(a−1,p))c\cdot (\min(t+b,p)-\min(a-1,p))。

由于 (t,p)(t,p) 是 (t,l,r)(t,l,r) 差分而来的,不妨将这个贡献差分前后都减去 ctct,则贡献为 c⋅(min⁡(b,p−t)−min⁡(a−1,p))c\cdot (\min(b,p-t)-\min(a-1,p))。

考虑在 p−tp-t 处维护 c⋅min⁡(b,p−t)c\cdot \min(b,p-t) 的贡献,在 pp 处维护 c⋅min⁡(a−1,p)c \cdot \min(a-1,p) 的贡献。于是问题变成维护一个序列,每次在 xx 处加 c⋅min⁡(k,x)c \cdot \min(k,x),同时单点查询,用两个树状数组即可维护。

最后的问题是如何找到每个 S−iS-i 覆盖的平行四边形,其实就是要找到每个 SiS_i 左边第一个 >Si> S_i 和右边第一个 ≥Si\ge S_i 的位置,正反做两次单调栈即可。

总时间复杂度 O(nlog⁡n)\mathcal O(n \log n)。

const int N = 2e5 + 7;
int n, m, l[N], r[N];
ll a[N], ans[N];
struct P {
	int a, b;
	ll c;
	inline P() {}
	inline P(int a, int b, ll c) : a(a), b(b), c(c) {}
};
vector<P> p[N];
struct Q {
	int p, i, o;
	inline Q() {}
	inline Q(int p, int i, int o) : p(p), i(i), o(o) {}
};
vector<Q> q[N];
struct BIT {
	vector<ll> c;
	inline BIT(int n = N) { c.resize(n); }
	inline void add(ui x, ll k) {
		while (x < c.size()) c[x] += k, x += x & -x;
	}
	inline ll ask(ui x) {
		ll k = 0;
		while (x) k += c[x], x -= x & -x;
		return k;
	}
};

int main() {
	rd(n, m), rda(a, n);
	for (int i = 1, t, l, r; i <= m; i++) {
		rd(t, l, r);
		q[t].pb(Q(r, i, 1));
		q[t].pb(Q(l - 1, i, -1));
	}
	stack<int> st;
	for (int i = 1; i <= n; i++) {
		while (st.size() && a[st.top()] <= a[i]) st.pop();
		l[i] = st.size() ? st.top() : 0;
		st.push(i);
	}
	while (st.size()) st.pop();
	for (int i = n; i; i--) {
		while (st.size() && a[st.top()] < a[i]) st.pop();
		r[i] = st.size() ? st.top() : n + 1;
		st.push(i);
	}
	for (int i = 1; i <= n; i++) {
		p[0].pb(P(i, i, a[i]));
		if (l[i]) p[i-l[i]].pb(P(i, l[i], -a[i]));
		if (r[i] != n + 1) p[r[i]-i].pb(P(r[i], i, -a[i]));
		if (l[i] && r[i] != n + 1) p[r[i]-l[i]].pb(P(r[i], l[i], a[i]));
	}
	BIT cx1 = BIT(N << 1 | 1), ck1 = BIT(N << 1 | 1), cx2, ck2;
	for (int t = 0; t <= n; t++) {
		for (P p : ::p[t]) {
			int a = p.a, b = p.b;
			ll c = p.c;
			cx1.add(1, c), cx1.add(b + n + 1, -c);
			ck1.add(b + n + 1, c * b);
			cx2.add(1, c), cx2.add(a, -c);
			ck2.add(a, c * (a - 1));
		}
		for (Q o : ::q[t]) {
			int p = o.p;
			ans[o.i] += o.o * cx1.ask(p - t + n + 1) * (p - t);
			ans[o.i] += o.o * ck1.ask(p - t + n + 1);
			ans[o.i] -= o.o * cx2.ask(p + 1) * p;
			ans[o.i] -= o.o * ck2.ask(p + 1);
		}
	}
	for (int i = 1; i <= m; i++) print(ans[i]);
	return 0;
}

标签:DP、二分、前缀和/差分、区间 DP、单调栈、扫描线、最短路、树状数组

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…