NOIP 2023 周赛 1 题解

A. 「JOISC 2014」巴士走读

summarization

\(n\) 个点和 \(m\) 辆巴士,每个巴士在 \(X_i\) 时从 \(A_i\) 出发,\(Y_i\) 时到达 \(B_i\),若要乘坐一辆巴士,在 \(\le X_i\) 时到达 \(A_i\) 即可。给定 \(Q\) 个询问 \(L_i\),询问若要到达 \(n\) 号点的时间 \(\le L_i\),最晚何时从 \(1\) 号点出发。

solution

考虑预处理出一些 \(l_i,r_i\),表示 \(l_i\) 时从 \(1\) 号点出发,\(r_i\) 时可以到达 \(n\) 号点,则在回答第 \(x\) 个询问时只需要找到满足 \(r_i\le L_x\) 的最大的 \(l_i\) 即可。

由于到达 \(n\) 号点的时间只可能是 \(B_i=n\) 的巴士的 \(Y_i\), 所以考虑枚举所有 \(B_i=n\) 的边,确定到达 \(n\) 号点的时间,然后在反向图上跑 Dijkstra,一条边能走的条件是到达点 \(B_i\) 的时间 \(\ge Y_i\),则到达 \(A_i\) 的时间为 \(X_i\)

但是这样肯定超时,考虑优化。

先将 \(A_i,B_i\) 相同的边按 \(Y_i\) 升序排序,访问时优先访问 \(Y_i\) 小的。不难发现如果一条边在之前的 Dijkstra 中经过,则这条边就不用访问了。

这是因为在先后两次 Dij 中,到达 \(n\) 的时间是变大的。而当在两次 Dij 中访问到 \(B_i\) 时,这个点此时的时间是不变的。而此时先的 Dij 若能访问这条边则已经访问了,且先的 Dij 对答案的贡献比后的 Dij 要大,所以后的 Dij 就不需要访问了。

所以所有的边只需要被访问一次,时间复杂度得到保证。

code

#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
CI N = 1e5, M = 3e5; int n, m, cur[M + 5], dis[N + 5]; struct node {int A, X, Y;}; vector <node> G[N + 5]; vector <pii> ans; bool vis[N + 5];
priority_queue <pii> q;
bool cmp (node a, node b) {return a.Y < b.Y;}
void Dij (node a) {
	RI i, j; Mt (vis, 0); vis[n] = 1; if (dis[a.A] >= a.X) return ; dis[a.A] = a.X; q.push (mk(a.X, a.A)); W (! q.empty ()) {
		pii p = q.top (); q.pop (); if (vis[p.se]) continue; vis[p.se] = 1; int sz = G[p.se].size ();
		for (i = cur[p.se]; i < sz; ++ i) {
			cur[p.se] = i; if (p.fi < G[p.se][i].Y) break; int to = G[p.se][i].A; if (dis[to] < G[p.se][i].X) dis[to] = G[p.se][i].X, q.push (mk (dis[to], to));
		}
	} ans.pb (mk (a.Y, dis[1]));
}
int main () {
	RI i, j; for (Read (n, m), i = 1; i <= m; ++ i) {int a, b, x, y; Read (a, b, x, y); G[b].pb ((node){a, x, y});} for (i = 1; i <= n; ++ i) sort (G[i].begin (), G[i].end (), cmp);
	Mt (dis, -1); ans.pb (mk (0, -1)); for (i = 0; i < G[n].size (); ++ i) Dij (G[n][i]); int Q; Read (Q);
	W (Q --) {int x; Read (x); printf ("%d\n", (--upper_bound (ans.begin (), ans.end (), mk (x, 0x7fffffff))) -> se);}
	return 0; 
}

B. 「JOISC 2014」有趣的家庭菜园

summarization

给定一个序列,每次操作只能交换相邻的两个数,问最少几次操作后可以将序列变为单峰。

solution

若将操作前序列中的数组从左到右标号 \(1\sim n\),则所有操作后交换的次数就是此时标号的逆序对个数。所以我们将最大的放在中间,然后将其他的数由大到小依次放在已有数字的左边或右边,哪边逆序对小就放在哪边,最后逆序对的个数就是最少的操作次数,注意相同数字放置的细节。

code

#define pb push_back
CI N = 3e5; struct node {int num, id;} a[N + 5]; int n, b[N + 5]; vector <int> v;
bool cmp (node x, node y) {return x.num > y.num;}
int lowbit (int x) {return x & -x;}
void add (int id, int x) {for (; id; id -= lowbit (id)) b[id] += x;}
int ask (int id) {int sum = 0; for (; id <= n; id += lowbit (id)) sum += b[id]; return sum;}
int main () {
	RI i, j; for (Read (n), i = 1; i <= n; ++ i) {int x; Read (x); a[i].num = x; a[i].id = i;} sort (a + 1, a + n + 1, cmp);
	int lst = 0x7fffffff, now = 0; ll ans = 0; for (i = 1; i <= n; ++ i) {
		if (lst != a[i].num) {for (auto x : v) add (x, 1), ++ now; v.clear ();} ans += min (ask (a[i].id), now - ask (a[i].id)); v.pb (a[i].id); lst = a[i].num;
	} printf ("%lld\n", ans);
	return 0; 
}

C. 「JOISC 2014」历史研究

summarization

给出一个序列和若干个询问 \(l_i,r_i\),需要求出在 \(l_i\sim r_i\) 之间的数中相同的数乘以相同的数的个数的乘积的最大值。(比如 7 8 9 8 7,则要求的值就是 \(\max(7\times2,8\times2,9\times1)=16\)

solution

emmm,回滚莫队板子题,不再赘述。

code

#define int ll
CI N = 1e5; int n, q, a[N + 5], b[N + 5], Tng[N + 5], ans[N + 5];
int bk, bn, bl[N + 5], br[N + 5], bi[N + 5];
struct node {
	int l, r, id;
	friend bool operator < (node x, node y) {
		return bi[x.l] == bi[y.l] ? x.r < y.r : x.l < y.l;
	}
}qn[N + 5];
int query (int l, int r) {
	RI i, j; int now = 0; for (i = l; i <= r; ++ i) ++ Tng[a[i]], now = max (now, Tng[a[i]] * b[a[i]]); for (i = l; i <= r; ++ i) -- Tng[a[i]]; return now;
}
main () {
	RI i, j; for (Read (n, q), i = 1; i <= n; ++ i) Read (a[i]), b[i] = a[i]; sort (b + 1, b + n + 1); int tot = unique (b + 1, b + n + 1) - b - 1;
	for (i = 1; i <= n; ++ i) a[i] = lower_bound (b + 1, b + tot + 1, a[i]) - b; bk = sqrt (n); bn = ceil (n * 1.0 / bk);
	for (i = 1; i <= bn; ++ i) bl[i] = (i - 1) * bk + 1, br[i] = i * bk; br[bn] = n; for (i = 1; i <= n; ++ i) bi[i] = (i - 1) / bk + 1;
	int qq = 0; for (i = 1; i <= q; ++ i) {
		int X, Y; Read (X, Y); if (bi[X] == bi[Y]) ans[i] = query (X, Y); else ++ qq, qn[qq].l = X, qn[qq].r = Y, qn[qq].id = i;
	} sort (qn + 1, qn + qq + 1);
	int L, R, mx, lst; bool flag = 0; for (i = 1; i <= qq; ++ i) {
		if (bi[qn[i].l] != bi[qn[i - 1].l]) flag = 1; if (flag) {
			Mt (Tng, 0); R = br[bi[qn[i].l]]; mx = 0; lst = 0; flag = 0;
		}
		W (R < qn[i].r) {++ R; ++ Tng[a[R]]; mx = lst = max (lst, Tng[a[R]] * b[a[R]]);} L = br[bi[qn[i].l]] + 1;
		W (L > qn[i].l) {-- L; ++ Tng[a[L]]; mx = max (mx, Tng[a[L]] * b[a[L]]);} ans[qn[i].id] = mx; mx = lst; L = br[bi[qn[i].l]] + 1;
		W (L > qn[i].l) {-- L; -- Tng[a[L]];}
	} for (i = 1; i <= q; ++ i) printf ("%lld\n", ans[i]);
	return 0; 
}

D. 「JOISC 2014」水壶

summarization

emmm,我概括不来了,看原题吧(精简了一下):

IOI 市被分成 \(H\) 行,每行包含 \(W\) 块区域。每个区域都是建筑物、原野、墙壁之一。
IOI 市有 \(P\) 个区域是建筑物,坐标分别为 \((A_1, B_1), (A_2, B_2), \ldots, (A_P, B_P)\)
JOI 君只能进入建筑物与原野,而且每次只能走到相邻的区域中,且不能移动到市外。

JOI 君在原野上每走过一个区域都需要 1 升水。大小为 \(x\) 的水壶最多可以装 \(x\) 升水,建筑物里有自来水可以将水壶装满。

JOI 君决定携带尽量小的水壶移动,问最少需要多大的水壶。

现在给出 IOI 市的地图和 \(Q\) 个询问,第 \(i\) 个询问包含两个整数 \(S_i, T_i\),对于每个询问,请输出:要从建筑物 \(S_i\) 移动到 \(T_i\),至少需要多大的水壶?

solution

首先我们将点阵图转化为 \(n\) 个点,\(n^2\) 条边,边权为两点间的最短距离的一个图,则考虑在这个图上求出 \(S_i\)\(T_i\) 需要的水壶大小,发现从 \(S_i\)\(T_i\) 所要的水壶大小就是从 \(S_i\) 走到 \(T_i\) 时经过边的边权的最大值。

所以在这个图的最小生成树上走,答案一定最优。因为最小生成树是联通的,且如果走到了一条最小生成树之外的边,这条边的权值一定大于最小生成树内的边。

求出 \(n^2\) 条边再求最小生成树显然不可能,考虑只求出我们需要的一些边。

我们考虑多个起点的 BFS,从所有建筑物开始 BFS,每走一步记录到起点建筑物的距离。当两条路相遇时,则说明有一条最短的路从一个建筑物到另一个建筑物。这种方式没有列出所有存在的边,但是保证了这些边的边权都是最小的那一批,所以不影响最小生成树的建立。

建立完最小生成树后,考虑如何回答询问。

使用倍增 LCA,在维护节点 \(x\) 的第 \(2^i\) 级祖先时顺便维护节点 \(x\) 到其 \(2^i\) 级祖先的路径上边权的最大值即可,询问时向上跳找 LCA 时顺便更新答案。

code

#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
CI HW = 2e3, N = 2e5, M = 4e6, dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1}; bool c[HW + 5][HW + 5]; int h, w, n, q, etot = 0, A[N + 5], B[N + 5];
struct node {int u, v, ww;} e[M + 5]; pii vis[HW + 5][HW + 5]; vector <pii> G[N + 5]; int fa[N + 5];
int f[N + 5][25], mx[N + 5][25], dep[N + 5], lg[N + 5];
void bfs () {
	RI i, j; queue <pii> q; for (i = 1; i <= n; ++ i) q.push (mk (A[i], B[i])), vis[A[i]][B[i]].fi = i;
	W (! q.empty ()) {
		pii p = q.front (); q.pop ();
		for (i = 0; i < 4; ++ i) {
			int xx = p.fi + dx[i], yy = p.se + dy[i]; if (c[xx][yy]) continue;
			if (! (xx >= 1 && xx <= h && yy >= 1 && yy <= w)) continue;
			if (vis[xx][yy].fi) {
				if (vis[xx][yy].fi != vis[p.fi][p.se].fi) e[++ etot] = (node){vis[xx][yy].fi, vis[p.fi][p.se].fi, vis[xx][yy].se + vis[p.fi][p.se].se};
			} else vis[xx][yy].fi = vis[p.fi][p.se].fi, vis[xx][yy].se = vis[p.fi][p.se].se + 1, q.push (mk (xx, yy));
		}
	}
}
void fainit () {RI i, j; for (i = 0; i <= n; ++ i) fa[i] = i;}
int find (int x) {return fa[x] == x ? x : fa[x] = find (fa[x]);}
void unionn (int x, int y) {x = find (x); y = find (y); fa[y] = x;}
bool cmp (node x, node y) {return x.ww < y.ww;}
void kruskal () {
	RI i, j; fainit (); sort (e + 1, e + etot + 1, cmp); int sum = 0; for (i = 1; i <= etot; ++ i) {
		int x = e[i].u, y = e[i].v, now = e[i].ww; if (find (x) == find (y)) continue;
		unionn (x, y); G[x].pb (mk (y, now)); G[y].pb (mk (x, now)); ++ sum; if (sum == n - 1) break;
	}
}
void initLCA () {RI i, j; for (i = 1; i <= n; ++ i) lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);}
void dfs (int now, int Fa, int val) {
	RI i, j; f[now][0] = Fa; mx[now][0] = val; dep[now] = dep[Fa] + 1;
	for (i = 1; i <= lg[dep[now]]; ++ i) f[now][i] = f[f[now][i - 1]][i - 1], mx[now][i] = max (mx[now][i - 1], mx[f[now][i - 1]][i - 1]);
	for (auto x : G[now]) if (x.fi != Fa) dfs (x.fi, now, x.se);
}
int LCA (int x, int y) {
	RI i, j; if (dep[x] < dep[y]) swap (x, y); int ans = 0; W (dep[x] > dep[y]) ans = max (ans, mx[x][lg[dep[x] - dep[y]] - 1]), x = f[x][lg[dep[x] - dep[y]] - 1]; if (x == y) return ans;
	for (i = lg[dep[x]] - 1; i >= 0; -- i) if (f[x][i] != f[y][i]) ans = max (ans, max (mx[x][i], mx[y][i])), x = f[x][i], y = f[y][i]; ans = max (ans, max (mx[x][0], mx[y][0])); return ans;
}
int main () {
	RI i, j; for (Read (h, w, n, q), i = 1; i <= h; ++ i) for (j = 1; j <= w; ++ j) {char ch; Readc (ch); c[i][j] = (ch == '#');}
	for (i = 1; i <= n; ++ i) Read (A[i], B[i]); bfs (); kruskal ();
	initLCA (); for (i = 1; i <= n; ++ i) if (! dep[i]) dfs (i, 0, 0);
	W (q --) {
		int x, y; Read (x, y); if (find (x) != find (y)) printf ("-1\n"); else printf ("%d\n", LCA (x, y));
	}
	return 0; 
}
posted @ 2023-05-30 23:08  ClapEcho233  阅读(36)  评论(0编辑  收藏  举报