JOISC 2022 乱做
作者:互联网
非传统题不做。
Day1 T1 Jail
给定一棵 \(n\) 个点的树,有 \(m\) 个人,第 \(i\) 个人从 \(s_i\) 出发要到 \(t_i\),每次可以指定一个人走一条边。问是否存在一种方案让每个人都到 \(t_i\),且满足任何两个人不同时出现在同一个节点。
\(m \leq n \leq 1.2 \times 10^5\),\(s_i\) 互不相同,\(t_i\) 互不相同,\(s_i \neq t_i\)。
solution
我们断言,一个人的移动一定是连续的,这点通过调整容易说明。考虑什么时候无解,感性理解一下发现就是有若干个人的路径形成了一个环的结构,这启发我们试着对这 \(n\) 个人移动的顺序进行约束。具体来说,对于两个人 \(i,j\),若 \(s_i \in (s_j,t_j)\),那么我们连边 \(i \to j\) 表示 \(i\) 要在 \(j\) 之前移动;若 \(t_i \in (s_j,t_j)\),那么我们连边 \(j \to i\) 表示 \(j\) 要在 \(i\) 之前移动。然后跑拓扑排序即可,无解当且仅当连出来的图成环。连边用倍增优化,时间复杂度 \(O((n+m) \log n)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;
inline int read() {
int x = 0, w = 1;char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * w;
}
const int MN = 2e5 + 5;
const int MM = 5e6 + 5;
const int Mod = 1e9 + 7;
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
inline int qPow(int a, int b = Mod - 2, int ret = 1) {
while (b) {
if (b & 1) ret = ret * a % Mod;
a = a * a % Mod, b >>= 1;
}
return ret;
}
#define dbg
int N, M, s[MN], t[MN];
int dep[MN], deg[MM], fa[MN][20], up[MN][20], down[MN][20], id;
int dfn[MN], dfc, sz[MN];
vector <int> e[MN], w[MM];
queue <int> q;
inline void DFS(int u, int pr) {
dep[u] = dep[fa[u][0] = pr] + 1;
dfn[u] = ++dfc, sz[u] = 1;
up[u][0] = ++id;
down[u][0] = ++id;
for (int i = 1; i <= 17; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
up[u][i] = ++id;
w[up[u][i - 1]].pb(id);
if (fa[u][i - 1]) w[up[fa[u][i - 1]][i - 1]].pb(id);
down[u][i] = ++id;
w[id].pb(down[u][i - 1]);
if (fa[u][i - 1]) w[id].pb(down[fa[u][i - 1]][i - 1]);
}
for (int v : e[u])
if (v != pr) DFS(v, u), sz[u] += sz[v];
}
inline void add1(int id, int x, int y) {
if (dfn[x] <= dfn[y] && dfn[y] < dfn[x] + sz[x]) {
for (int i = 17; i >= 0; i--)
if (dep[fa[y][i]] >= dep[x]) w[up[y][i]].pb(id), y = fa[y][i];
} else {
x = fa[x][0];
if (dep[x] < dep[y]) swap(x, y);
for (int i = 17; i >= 0; i--)
if (dep[fa[x][i]] >= dep[y]) w[up[x][i]].pb(id), x = fa[x][i];
if (x == y) w[up[x][0]].pb(id);
else {
for (int i = 17; i >= 0; i--)
if (fa[x][i] != fa[y][i])
w[up[x][i]].pb(id), w[up[y][i]].pb(id), x = fa[x][i], y = fa[y][i];
w[up[x][0]].pb(id), w[up[y][1]].pb(id);
}
}
}
inline void add2(int id, int x, int y) {
if (dfn[y] <= dfn[x] && dfn[x] < dfn[y] + sz[y]) {
for (int i = 17; i >= 0; i--)
if (dep[fa[x][i]] >= dep[y]) w[id].pb(down[x][i]), x = fa[x][i];
} else {
y = fa[y][0];
if (dep[x] < dep[y]) swap(x, y);
for (int i = 17; i >= 0; i--)
if (dep[fa[x][i]] >= dep[y]) w[id].pb(down[x][i]), x = fa[x][i];
if (x == y) w[id].pb(down[x][0]);
else {
for (int i = 17; i >= 0; i--)
if (fa[x][i] != fa[y][i])
w[id].pb(down[x][i]), w[id].pb(down[y][i]), x = fa[x][i], y = fa[y][i];
w[id].pb(down[x][0]), w[id].pb(down[y][1]);
}
}
}
inline void work() {
N = read(), dfc = 0;
for (int i = 1; i <= N; i++) e[i].clear();
for (int i = 1; i <= id; i++) w[i].clear(), deg[i] = 0;
for (int i = 1, x, y; i < N; i++) {
x = read(), y = read();
e[x].pb(y), e[y].pb(x);
}
M = read(), id = M;
DFS(1, 0);
for (int i = 1, x, y; i <= M; i++) {
x = read(), y = read();
w[i].pb(up[x][0]), w[down[y][0]].pb(i);
add1(i, x, y), add2(i, x, y);
}
int fl = 0;
for (int i = 1; i <= id; i++) for (int x : w[i]) deg[x]++;
for (int i = 1; i <= id; i++) if (!deg[i]) q.push(i);
while (!q.empty()) {
int x = q.front(); q.pop(), fl += x <= M;
for (int y : w[x])
if (!(--deg[y])) q.push(y);
}
puts(fl == M ? "Yes" : "No");
}
signed main(void) {
int T = read();
while (T--) work();
return 0;
}
Day1 T2 Sightseeing in Kyoto
给定 \(n \times m\) 的网格图,第 \(i\) 行所有边权值为 \(a_i\),第 \(j\) 列所有边权值为 \(b_j\),每次只能向右或者向下移动,求 \((1,1)→(n,m)\) 的最短路。
\(n,m \leq 10^5\)。
solution
先考虑简单的情况,如果只经过一次转弯要从 \((i,j) \to (x,y)\),那么有两种可能的路径:\((i,j) \to (i,y) \to (x,y)\) 和 \((i,j) \to (x,j) \to (x,y)\)。这两条路径所对应的大小分别是 \((y-j)a_i + (x-i) b_y\) 和 \((x-i) b_j + (y-j) a_x\)。稍作化简可以得到当 \(\frac{a_x - a_i}{x - i} > \frac{b_y - b_j}{y - j}\) 时我们会选择第一条路,否则会选择第二条。
对于当前的所有行和列,把行和列分别看成点 \((i,a_i)\) 和 \((j,b_j)\),上式相当于比较了两个斜率。不妨先关注所有 \(2 \times 2\) 的小方格,处理出所有相邻位置(包括行和列)的斜率,那么对于斜率最大的两个点而言(假设是第 \(i\) 行和第 \(i+1\) 行),容易发现我们一定不会经过第 \(i+1\) 行,因此可以直接将第 \(i+1\) 行删去。
于是我们可以直接模拟上述过程,用 set 维护当前所有相邻点的斜率,每次取出最大的一个,删掉对应行 / 列并更新相邻点的斜率,如果删掉该行 / 列后 \((1,1)\) 和 \((n,m)\) 不连通就更新答案,维护一下从 \((n,m)\) 开始的已经确定的路径延伸到哪里就行了。时间复杂度 \(O((n+m) \log (n+m))\)。
还可以继续优化:回顾我们求解答案的过程,每次取斜率最大的位置删除,然后如果该位置是最靠右的点就计入答案;不难发现对于行和列而言,只有在下凸包上的点可能对答案产生贡献。于是我们可以直接对行和列求下凸包,这样相邻位置的斜率都是单调的,那么每次只会删除结尾位置的点,用双指针维护即可。当然也可以从前往后用双指针维护决策,反正是等价的。时间复杂度 \(O(n+m)\)。
code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;
inline int read() {
int x = 0, w = 1;char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * w;
}
const int MN = 1e5 + 5;
const int Mod = 1e9 + 7;
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
inline int qPow(int a, int b = Mod - 2, int ret = 1) {
while (b) {
if (b & 1) ret = ret * a % Mod;
a = a * a % Mod, b >>= 1;
}
return ret;
}
#define dbg
int N, M, a[MN], b[MN], p[MN], q[MN], l, r;
signed main(void) {
N = read(), M = read();
for (int i = 1; i <= N; i++) a[i] = read();
for (int i = 1; i <= M; i++) b[i] = read();
p[l = 1] = q[r = 1] = 1;
for (int i = 2; i <= N; i++) {
while (l > 1 && (a[i] - a[p[l]]) * (p[l] - p[l - 1]) <= (a[p[l]] - a[p[l - 1]]) * (i - p[l])) l--;
p[++l] = i;
}
for (int i = 2; i <= M; i++) {
while (r > 1 && (b[i] - b[q[r]]) * (q[r] - q[r - 1]) <= (b[q[r]] - b[q[r - 1]]) * (i - q[r])) r--;
q[++r] = i;
}
int x = 1, y = 1, s = 1, t = 1, ans = 0;
while (x < N || y < M) {
if (s == l || (t != r && (a[p[s + 1]] - a[p[s]]) * (q[t + 1] - q[t]) > (b[q[t + 1]] - b[q[t]]) * (p[s + 1] - p[s])))
ans += (q[t + 1] - q[t]) * a[x], y = q[++t];
else
ans += (p[s + 1] - p[s]) * b[y], x = p[++s];
}
printf("%lld\n", ans);
return 0;
}
Day1 T3 Misspelling
对于由小写字母组成的字符串 \(S\),定义字符串 \(T_i\) 为 \(S\) 删去第 \(i\) 个字符并将前后字符串相接所得的字符串。给定 \(m\) 个条件,每个条件形如 \((s_i,t_i)\) 表示 \(T_{s_i}\) 的字典序不大于 \(T_{b_i}\)。求有多少个长度为 \(n\) 的字符串 \(S\) 满足所有条件。答案对 \(10^9 + 7\) 取模。
\(n,m \leq 5 \times 10^5\)。
标签:dep,fa,int,pb,JOISC,2022,id,define 来源: https://www.cnblogs.com/came11ia/p/16667178.html