誰にも掴めない風になりたい
6 月 10 日 ~ 6 月 24 日
被压力学考了,顺便考了一下 MO 预赛,所以还是没几个题。
CF 1830D Mex Tree
给定一棵
感性地想,我们希望让
但有时候这并不是最优的:我们把答案分成两部分,长度
注意到,vector
存 DP 答案并在计算完成后立即销毁,总时间复杂度
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 2e5 + 5, B = 5e2;
constexpr LL inf = 1e18;
int n, sz[N]; vector <int> e[N];
LL ans, tmp[B + 5][2]; vector <LL> f[N][2];
void dfs(int u, int ff) {
sz[u] = 1;
f[u][0].push_back(inf), f[u][0].push_back(0);
f[u][1].push_back(inf), f[u][1].push_back(1);
for (auto v : e[u]) if (v != ff) {
dfs(v, u);
LL mi0 = inf, mi1 = inf;
for (int i = 1; i <= min(B, sz[v]); i++) mi0 = min(mi0, f[v][0][i]), mi1 = min(mi1, f[v][1][i]);
for (int i = 1; i <= min(B, sz[u]); i++) {
tmp[i][0] = f[u][0][i], f[u][0][i] = inf;
tmp[i][1] = f[u][1][i], f[u][1][i] = inf;
}
for (int i = min(B, sz[u]) + 1; i <= min(B, sz[u] + sz[v]); i++) f[u][0].push_back(inf), f[u][1].push_back(inf);
for (int i = 1; i <= min(B, sz[u]); i++) {
for (int j = 1; j <= min(B, sz[v]) && i + j <= B; j++) {
f[u][0][i + j] = min(f[u][0][i + j], tmp[i][0] + f[v][0][j] + 1LL * i * j);
f[u][1][i + j] = min(f[u][1][i + j], tmp[i][1] + f[v][1][j] + 2LL * i * j);
}
}
for (int i = 1; i <= min(B, sz[u]); i++) {
f[u][0][i] = min(f[u][0][i], tmp[i][0] + mi1);
f[u][1][i] = min(f[u][1][i], tmp[i][1] + mi0);
}
sz[u] += sz[v];
f[v][0].clear(), f[v][0].shrink_to_fit();
f[v][1].clear(), f[v][1].shrink_to_fit();
}
}
void clr() {
ans = 0;
for (int i = 1; i <= n; i++) {
e[i].clear();
f[i][0].clear(), f[i][0].shrink_to_fit();
f[i][1].clear(), f[i][1].shrink_to_fit();
}
}
void solve() {
cin >> n;
for (int i = 1, x, y; i < n; i++) {
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
ans = inf;
dfs(1, 0);
for (int i = 1; i <= min(n, B); i++) ans = min(ans, min(f[1][0][i], f[1][1][i]));
ans = 1LL * n * n - ans;
cout << ans << "\n";
clr();
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
int t; cin >> t;
while (t--) solve();
return 0;
}
CF 1830E Bully Sort
给定一个长为
- 设
为最大的满足 的元素 所在的位置, 为最小的满足 的元素 所在的位置,交换 。
定义
先考虑怎么求
发现直接考虑一个数的移动次数或者每个位置被操作的次数都很困难,我们尝试找到一些容易计算的量,并发掘其和所求量之间的关系。
注意到一个关键性质:设
前半部分容易维护,后半部分先预处理出逆序对数,每次更新需要查询区间内
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 5e5, B = 2.3e3, M = N / B + 5;
int n, m, q, a[N + 5];
int bl[M], br[M], len[M], bid[N], t[M][B + 5];
struct BIT {
int c[N];
void upd(int x, int v) {
for (int i = x; i <= n; i += i & -i) c[i] += v;
}
int qry(int x) {
int res = 0;
for (int i = x; i; i -= i & -i) res += c[i];
return res;
}
} tr;
void mdf(int x, int y) {
int l = bid[x], r = bid[y];
if (l == r)
return swap(a[x], a[y]);
int dl = lower_bound(t[l] + 1, t[l] + len[l] + 1, a[x]) - t[l];
int dr = lower_bound(t[r] + 1, t[r] + len[r] + 1, a[y]) - t[r];
for (int i = dl; i < len[l]; i++)
t[l][i] = t[l][i + 1];
for (int i = dr; i < len[r]; i++)
t[r][i] = t[r][i + 1];
swap(a[x], a[y]);
dl = lower_bound(t[l] + 1, t[l] + len[l], a[x]) - t[l];
dr = lower_bound(t[r] + 1, t[r] + len[r], a[y]) - t[r];
for (int i = len[l]; i > dl; i--)
t[l][i] = t[l][i - 1];
for (int i = len[r]; i > dr; i--)
t[r][i] = t[r][i - 1];
t[l][dl] = a[x];
t[r][dr] = a[y];
}
int qry(int l, int r) {
int res = 0;
int L = bid[l], R = bid[r];
if (L == R) {
for (int i = l + 1; i <= r - 1; i++) {
if (a[l] > a[i]) res++;
if (a[i] > a[r]) res++;
}
if (a[l] > a[r]) res++;
return res;
}
for (int i = l + 1; i <= br[L]; i++) {
if (a[l] > a[i]) res++;
if (a[i] > a[r]) res++;
}
for (int i = bl[R]; i <= r - 1; i++) {
if (a[l] > a[i]) res++;
if (a[i] > a[r]) res++;
}
if (a[l] > a[r]) res++;
for (int i = L + 1; i <= R - 1; i++) {
int dl = lower_bound(t[i] + 1, t[i] + len[i] + 1, a[l]) - t[i];
int dr = lower_bound(t[i] + 1, t[i] + len[i] + 1, a[r]) - t[i];
res += dl - 1;
res += (len[i] - dr + 1);
}
return res;
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
LL ans = 0;
for (int i = 1; i <= n; i++) ans += abs(i - a[i]);
for (int i = n; i >= 1; i--) {
ans -= tr.qry(a[i] - 1);
tr.upd(a[i], 1);
}
while (br[m] < n) {
m++;
bl[m] = br[m - 1] + 1, br[m] = min(n, bl[m] + B - 1), len[m] = (br[m] - bl[m] + 1);
for (int i = bl[m]; i <= br[m]; i++) {
bid[i] = m;
t[m][i - bl[m] + 1] = a[i];
}
sort(t[m] + 1, t[m] + len[m] + 1);
}
while (q--) {
int x, y;
cin >> x >> y;
ans -= abs(x - a[x]);
ans -= abs(y - a[y]);
ans += qry(x, y);
mdf(x, y);
ans += abs(x - a[x]);
ans += abs(y - a[y]);
ans -= qry(x, y);
cout << ans << "\n";
}
return 0;
}
UOJ 805【UR #25】设计草图
给定一个只包含 (
、)
和 ?
构成的字符串 ?
替换为 (
或)
的方式,使得区间内的字符能按顺序组成一个合法的括号序列。求
容斥成算没救的区间个数,我们考虑什么时候一个区间肯定是没救的。
将 (
视为 )
视为 ?
都换成 (
后仍然存在某个前缀和 ?
都换成 )
后仍存在某个后缀和
然后我们发现这个条件好像是充要的。考虑枚举左端点,计算有多少个右端点对应的区间没救。对于每个位置 ?
都换成 (
后仍然存在某个前缀和 ?
都换成 )
后仍存在某个后缀和
关于预处理:以 ?
换成 (
做一遍前缀和,那么对于 )
。倒着枚举,对于每个数 )
的位置
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 1e6 + 5;
int n; string str;
int sum[N], c[N], d[N], tmp[N * 2]; LL ans;
vector <int> v[N];
struct BIT {
int c[N];
void mdf(int x, int v) {
for (int i = x; i <= n; i += i & -i) c[i] += v;
}
int qry(int x) {
int ret = 0;
for (int i = x; i; i -= i & -i) ret += c[i];
return ret;
}
} t0, t1;
int calc(int l, int r) {
return (r - l + 1) / 2;
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> str;
str = ' ' + str;
n = str.length() - 1;
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (str[i] == ')' ? -1 : 1);
int mi = n;
for (int i = n; i >= 1; i--) {
mi = min(mi, sum[i]);
if (str[i] == ')') c[i] = -1;
else if (!tmp[sum[i - 1] + n]) c[i] = n;
else c[i] = tmp[sum[i - 1] + n];
if (str[i + 1] == ')') tmp[sum[i] + n] = i;
}
fill(tmp + 1, tmp + 2 * n + 1, 0);
for (int i = n; i >= 1; i--) sum[i] = sum[i + 1] + (str[i] == '(' ? -1 : 1);
for (int i = 1; i <= n; i++) {
if (str[i] == '(') d[i] = i + 1;
else if (!tmp[sum[i + 1] + n]) d[i] = 1;
else d[i] = tmp[sum[i + 1] + n];
if (str[i - 1] == '(') tmp[sum[i] + n] = i;
}
for (int i = 1; i <= n; i++) if (d[i] != -1) v[d[i] - 1].push_back(i);
for (int i = n; i >= 1; i--) {
if (c[i] == -1) continue;
ans += calc(i, c[i]);
for (auto it : v[i]) {
if (it % 2 == 0) t0.mdf(it, 1);
if (it % 2 == 1) t1.mdf(it, 1);
}
if (i % 2 == 0) ans -= t1.qry(c[i]);
if (i % 2 == 1) ans -= t0.qry(c[i]);
}
cout << ans << "\n";
return 0;
}
UOJ 806【UR #25】见贤思齐
做法 from zky,感觉有点外星了。
设
然后比较神秘的一步出现了,设
直接维护
此时我们考虑最短的后缀
而一段后缀在树上就是
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 2e5 + 5, inf = 1e9;
int n, q, a[N], p[N];
int up[20][N], up1[20][N], up2[20][N];
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> p[i];
for (int i = 1; i <= n; i++) {
up[0][i] = p[i];
up1[0][i] = a[i];
up2[0][i] = a[i];
}
for (int j = 1; j <= 19; j++) {
for (int i = 1; i <= n; i++) {
int p = up[j - 1][i];
up[j][i] = up[j - 1][p];
up1[j][i] = max(up1[j - 1][i], up1[j - 1][p] + (1 << (j - 1)));
up2[j][i] = min(up2[j - 1][i], up2[j - 1][p]);
}
}
while (q--) {
int x, d;
cin >> x >> d;
int l = -1e9, r = 1e9;
int cur = 0;
for (int i = 19; i >= 0; i--) {
if (cur + (1 << i) <= d) {
int nl = max(l, up1[i][x] - d + cur);
int nr = min(r, up2[i][x]);
if (nl >= nr) {
continue;
}
l = nl;
r = nr;
x = up[i][x];
cur += 1 << i;
}
}
l = max(l, a[x] - d + cur);
if (l >= r) {
cout << r + d << "\n";
continue;
}
r = min(r, a[x]);
if (l >= r) {
cout << l + d << "\n";
continue;
}
}
return 0;
}
LG P9393 紫丁香
设
先考虑怎么处理一次询问。
二分答案,问题转化为给定一个位的集合 1
、*
组成的串 1
的位表示操作完后这一位要是 *
的位表示对这一位没有限制。
由于操作的形式是覆盖,我们不妨倒着考虑。考虑最后一次操作,设其对应的串为 *
的位,0
、1
或 -
中的任意一个。而对于 1
的位, 1
或 -
。假设某个 1
的位在 1
,那么这一位在更之前的操作中就没有限制了,于是我们可以在 *
。
于是我们可以按照如下过程进行判定:每次选一个合法的 1
的位在 *
,重复此过程直到 1
,我们只需要检验这些位置在 1
即可。
注意到,上述过程除了最后一步之外之和 1
的数量最小的串,*
的位的并,那么有 *
全改为 1
后的串为
总时间复杂度
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 23;
int n, m, q, f[1 << N], g[1 << N];
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n >> q;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
s = ' ' + s;
int x = 0, y = 0;
for (int j = 1; j <= m; j++) {
x *= 2, y *= 2;
if (s[j] != '0') x += 1;
if (s[j] == '1') y += 1;
}
g[x] |= y;
}
for (int s = (1 << m) - 1; s >= 1; s--) {
for (int i = 0; i < m; i++) {
if (((s >> i) & 1) == 0) g[s] |= g[s | (1 << i)];
}
}
for (int s = 1; s < (1 << m); s++) {
if (s & g[s]) f[s] = f[s ^ (s & g[s])];
else f[s] = s;
}
while (q--) {
int x = 0; string s;
cin >> s;
s = ' ' + s;
for (int i = 1; i <= m; i++) {
x = x * 2;
if (s[i] == '1') x += 1;
}
int cur = 0;
for (int i = m - 1; i >= 0; i--) {
if ((f[cur | (1 << i)] & x) == f[cur | (1 << i)]) cout << "1", cur |= (1 << i);
else cout << "0";
}
cout << "\n";
}
return 0;
}
LG P7838 「Wdoi-3」夜雀 treating
给定一个长为
转化一下题意:显然一开始就在
不妨先考察一下最后的
必要性是显然的,充分性随便构造一下就行了。于是我们要解决的问题变成,给定
考虑寻找更快的判定方式。设在
但这个东西似乎没有办法快速维护。 事实上我们有更强的结论:合法当且仅当对于所有
总时间复杂度
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 4e5 + 5;
int n, a[N], q[N];
#define m ((l + r) >> 1)
int tr[N << 1], tg[N];
void ptag(int x, int v) { tr[x] += v, tg[x] += v; }
void down(int x) {
if (tg[x]) ptag(x << 1, tg[x]), ptag(x << 1 | 1, tg[x]), tg[x] = 0;
}
void build(int x, int l, int r) {
if (l == r) return tr[x] = l, void();
build(x << 1, l, m), build(x << 1 | 1, m + 1, r);
tr[x] = min(tr[x << 1], tr[x << 1 | 1]);
}
void mdf(int x, int l, int r, int ql, int qr, int v) {
if (ql <= l && qr >= r) return ptag(x, v);
down(x);
if (ql <= m) mdf(x << 1, l, m, ql, qr, v);
if (qr > m) mdf(x << 1 | 1, m + 1, r, ql, qr, v);
tr[x] = min(tr[x << 1], tr[x << 1 | 1]);
}
#undef m
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= 2 * n + 1; i++) cin >> a[i], q[a[i]] = i;
build(1, 1, n);
int l = 1, r = 0, ans = 0;
while (r <= 2 * n + 1) {
if (tr[1] < 0) {
int pos = q[l];
if (pos == n + 1) { l += 1; continue; }
if (pos <= n) mdf(1, 1, n, pos, n, 1);
else mdf(1, 1, n, 2 * n + 1 - pos + 1, n, 1);
l += 1;
} else {
ans = max(ans, r - l + 1);
r += 1;
int pos = q[r];
if (pos == n + 1) continue;
if (pos <= n) mdf(1, 1, n, pos, n, -1);
else mdf(1, 1, n, 2 * n + 1 - pos + 1, n, -1);
}
}
cout << ans << "\n";
return 0;
}
LG P7735 [NOI2021] 轻重边
有一棵
- 给定两个点
和 ,对 到 路径上的所有点 ,将与 相连的所有边变为轻边。然后再将 到 路径上包含的所有边变为重边。 - 给定两个点
和 ,求当前 到 的路径上一共包含多少条重边。
考虑一条边
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 1e5 + 5;
int n, m, tim, a[N]; vector <int> e[N];
int sz[N], dep[N], dfn[N], dfc, top[N], val[N], par[N], son[N];
void dfs(int u, int cur) {
dep[u] = cur, sz[u] = 1, son[u] = 0;
for (auto v : e[u]) if (v != par[u]) {
par[v] = u;
dfs(v, cur + 1), sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs1(int u, int anc) {
val[dfn[u] = ++dfc] = a[u], top[u] = anc;
if (son[u]) dfs1(son[u], anc);
for (auto v : e[u]) if (v != par[u] && v != son[u]) {
dfs1(v, v);
}
}
struct SGT {
#define m ((l + r) >> 1)
int tr[N << 2], lc[N << 2], rc[N << 2], tg[N << 2];
void up(int x) {
tr[x] = tr[x << 1] + tr[x << 1 | 1] + (rc[x << 1] == lc[x << 1 | 1]);
lc[x] = lc[x << 1];
rc[x] = rc[x << 1 | 1];
}
void ptag(int x, int l, int r, int v) { tr[x] = r - l, lc[x] = rc[x] = tg[x] = v; }
void down(int x, int l, int r) {
if (tg[x]) ptag(x << 1, l, m, tg[x]), ptag(x << 1 | 1, m + 1, r, tg[x]), tg[x] = 0;
}
void build(int x, int l, int r) {
tg[x] = 0;
if (l == r) return tr[x] = 0, lc[x] = rc[x] = val[l], void();
build(x << 1, l, m), build(x << 1 | 1, m + 1, r);
up(x);
}
void mdf(int x, int l, int r, int ql, int qr, int v) {
if (ql <= l && qr >= r) return ptag(x, l, r, v), void();
down(x, l, r);
if (ql <= m) mdf(x << 1, l, m, ql, qr, v);
if (qr > m) mdf(x << 1 | 1, m + 1, r, ql, qr, v);
up(x);
}
int qry(int x, int l, int r, int ql, int qr) {
if (ql <= l && qr >= r) return tr[x];
int ret = 0; down(x, l, r);
if (ql <= m) ret += qry(x << 1, l, m, ql, qr);
if (qr > m) ret += qry(x << 1 | 1, m + 1, r, ql, qr);
if (ql <= m && qr > m) ret += (rc[x << 1] == lc[x << 1 | 1]);
return ret;
}
int get(int x, int l, int r, int p) {
if (l == r) return lc[x];
down(x, l, r);
if (p <= m) return get(x << 1, l, m, p);
return get(x << 1 | 1, m + 1, r, p);
}
#undef m
} t;
void mdf(int x, int y) {
++tim;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
t.mdf(1, 1, n, dfn[top[x]], dfn[x], tim);
x = par[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
t.mdf(1, 1, n, dfn[x], dfn[y], tim);
}
int qry(int x, int y) {
int ret = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ret += t.qry(1, 1, n, dfn[top[x]], dfn[x]);
ret += (t.get(1, 1, n, dfn[top[x]]) == t.get(1, 1, n, dfn[par[top[x]]]));
x = par[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
ret += t.qry(1, 1, n, dfn[x], dfn[y]);
return ret;
}
void clr() {
dfc = tim = 0;
for (int i = 1; i <= n; i++) e[i].clear();
}
void solve() {
cin >> n >> m;
for (int i = 1, x, y; i < n; i++) {
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
for (int i = 1; i <= n; i++) a[i] = ++tim;
dfs(1, 1), dfs1(1, 1);
t.build(1, 1, n);
while (m--) {
int ty, x, y;
cin >> ty >> x >> y;
if (ty == 1) {
mdf(x, y);
} else {
cout << qry(x, y) << "\n";
}
}
clr();
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
int t; cin >> t;
while (t--) solve();
return 0;
}
LG P8978 「DTOI-4」中位数
给定一个长度为
- 选择一个区间
满足 ,并将 中的所有数替换为这个区间的中位数。
求操作后
虽然样例明示了,但我们首先二分答案,转化为
对于题目给出的操作,我们有一些比较简单的观察:每次操作所选的区间一定是极大的。由此有一个简单的推论,如果有解,由于每次倍增,那么有操作次数
关键性质:对于任意
, 或 。 证明:
先证明相交必然包含:这点比较显然,考虑两次相交的操作
, ,由于在 操作之后 全是 ,所以让 直接包过去,即令 一定合法。 接下来只需要证明一定相交。我们使用调整法。
找到最后两个相邻且不交的区间
,则 。因为 ,所以一定存在 使得 ,且 。 不妨假设
,那么我们可以把 全部取消,替换成 的一个极大区间 ,满足 ,这样消去 的数量为 ,而 消去的 的数量显然不会超过 ,因此替换后 内 的数量一定增加,而操作数不增。 当
时同理。调整后,相邻且不交的区间的位置递减,因此调整总可以结束。故结论成立。
至此,我们可以设计 DP
根据操作区间的极大性,因此对于每个
设
利用单调性,使用扫描线 + 单调栈 + 线段树二分可以做到单次 check
考虑进一步利用递增性质,从后往前扫描线,那么加入区间
总时间复杂度
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 4e5 + 5;
int n, k, a[N], b[N], s[N];
int f[N], tag[N], buc[N << 1];
bool check(int x) {
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + (a[i] >= x ? 1 : -1);
}
memset(tag, 0, sizeof(tag));
for (int i = 1, lim = N; i <= n; i++) {
if (s[i - 1] < lim) lim = s[i - 1], tag[i] = 1;
}
memset(buc, 0, n + 2 << 3);
for (int i = 1; i <= n; i++) buc[n + s[i]] = i;
for (int i = n * 2 - 1; i >= 0; i--) buc[i] = max(buc[i], buc[i + 1]);
for (int i = 1; i <= n; i++) f[i] = i - 1;
for (int c = 1; c <= k && (1 << c - 1) <= n; c++) {
static int d[N], val[N], hd, tl;
hd = 1, tl = 0;
for (int i = n; i >= 1; i--) {
if (!tag[i]) continue;
int v = f[i] - i + 1 - (s[f[i]] - s[i - 1]);
while (hd <= tl && v >= val[tl]) tl--;
val[++tl] = v, d[tl] = f[i];
while (hd <= tl) {
int v = val[hd], p = buc[max(0, s[i - 1] + 1 - v + n)];
if (p >= d[hd]) { f[i] = p; break; }
hd++;
}
}
if (f[1] == n) return c <= k;
}
return 0;
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
sort(b + 1, b + n + 1);
int c = unique(b + 1, b + n + 1) - b - 1;
int l = 1, r = c, ans;
while (l <= r) {
int m = (l + r) >> 1;
if (check(b[m])) l = m + 1, ans = b[m];
else r = m - 1;
}
cout << ans << "\n";
return 0;
}
LG P7737 [NOI2021] 庆典
给定一个
缩点不影响可达性,我们先缩点。
假设缩点之后有两条边
于是我们要解决的就变成了树上问题。注意到,对于一段路径,如果其走法唯一,那么我们就可以把它缩成一条边,一起计算上面所有点的贡献。具体来说,我们将
沿用暴力做法:建原图和反图分别求出
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 3e5 + 5, M = 30;
int n, m, q, k, d[N];
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;
}
struct big_gragh {
int hd[N], t;
struct E { int to, nxt; } e[N * 2];
void add(int u, int v) {
e[++t].to = v, e[t].nxt = hd[u], hd[u] = t;
}
} G1, G2;
struct small_graph {
int hd[M], t;
struct E { int to, nxt, val; } e[M * 2];
void add(int u, int v, int w) {
e[++t].to = v, e[t].nxt = hd[u], e[t].val = w, hd[u] = t;
}
void clr() {
t = 0;
memset(hd, 0, sizeof(hd));
}
} G3, G4;
struct E {
int to, nxt;
} e[N * 2];
int hd[N], t;
void add(int u, int v) {
e[++t].to = v, e[t].nxt = hd[u], hd[u] = t;
}
int dfn[N], low[N], dfc, stk[N], tp, instk[N], sz[N], bcnt, col[N], ind[N];
void tarjan(int u) {
dfn[u] = low[u] = ++dfc;
instk[stk[++tp] = u] = 1;
for (int i = hd[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else {
if (instk[v]) low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
bcnt++;
int x = -1;
while (x != u) {
x = stk[tp--];
instk[x] = 0;
sz[bcnt]++;
col[x] = bcnt;
}
}
}
void toposort() {
queue <int> q;
for (int i = 1; i <= bcnt; i++) if (d[i] == 0) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = G1.hd[u]; i; i = G1.e[i].nxt) {
int v = G1.e[i].to;
if (--d[v] == 0) {
// cerr << "G2 add : " << u << " " << v << "\n";
G2.add(u, v), q.push(v), ind[v]++;
}
}
}
}
struct hash {
unordered_map <int, int> mp;
int all;
void clr() { mp.clear(), all = 0; }
int get(int x) {
if (mp[x] == 0) mp[x] = ++all;
return mp[x];
}
} H;
int p[N][20], st[N], tim, dep[N], sum[N], lg[N];
void dfs(int u, int ff) {
st[u] = ++tim, sum[u] = sum[ff] + sz[u];
dep[u] = dep[p[u][0] = ff] + 1;
for (int i = 1; i <= lg[dep[u]]; i++) p[u][i] = p[p[u][i - 1]][i - 1];
for (int i = G2.hd[u]; i; i = G2.e[i].nxt) {
int v = G2.e[i].to;
if (v != ff) dfs(v, u);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
while (dep[x] > dep[y]) x = p[x][lg[dep[x] - dep[y]] - 1];
if (x == y) return x;
for (int i = lg[dep[x]]; i >= 0; i--) if (p[x][i] != p[y][i]) x = p[x][i], y = p[y][i];
return p[x][0];
}
int vis1[M], vis2[M], mark[M], val[M];
void clr() {
memset(vis1, 0, sizeof(vis1));
memset(vis2, 0, sizeof(vis2));
memset(mark, 0, sizeof(mark));
memset(val, 0, sizeof(val));
}
int solve(int x, int y) {
int ret = 0;
queue <int> q;
q.push(H.get(x));
while (!q.empty()) {
int u = q.front(); q.pop();
mark[u] = 1;
for (int i = G3.hd[u]; i; i = G3.e[i].nxt) {
if (vis1[i] == 0) {
vis1[i] = 1, q.push(G3.e[i].to);
}
}
}
q.push(H.get(y));
while (!q.empty()) {
int u = q.front(); q.pop();
if (mark[u]) ret += val[u], mark[u] = 0;
for (int i = G4.hd[u]; i; i = G4.e[i].nxt) {
if (vis2[i] == 0) {
vis2[i] = 1;
if (vis1[i] == 1) ret += G4.e[i].val, vis1[i] = 0;
q.push(G4.e[i].to);
}
}
}
clr();
return ret;
}
bool cmp(int i, int j) {
return st[i] < st[j];
}
signed main() {
// ios :: sync_with_stdio(false);
// cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> m >> q >> k;
for (int i = 1; i <= n; i++) lg[i] = lg[i >> 1] + 1;
for (int i = 1, x, y; i <= m; i++) {
x = read(), y = read(); add(x, y);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
// for (int i = 1; i <= n; i++) cerr << col[i] << " \n"[i == n];
for (int u = 1; u <= n; u++) {
for (int i = hd[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (col[u] != col[v]) G1.add(col[u], col[v]), d[col[v]]++;
}
}
toposort();
int root = 0;
for (int i = 1; i <= bcnt; i++) if (ind[i] == 0) root = i;
// cerr << root << "\n";
dfs(root, 0);
// for (int i = 1; i <= bcnt; i++) cerr << sz[i] << " \n"[i == bcnt];
// for (int i = 1; i <= bcnt; i++) cerr << sum[i] << " \n"[i == bcnt];
for (int i = 1, x, y; i <= q; i++) {
static int que[30], c;
G3.clr(), G4.clr(), H.clr(), c = 0;
x = read(), y = read();
x = col[x];
y = col[y];
que[1] = x, que[2] = y;
for (int j = 1, u, v; j <= k; j++) {
u = read(), v = read();
u = col[u];
v = col[v];
if (u == v) continue;
// cerr << "add :" << H.get(u) << " " << H.get(v) << " " << 0 << "\n";
G3.add(H.get(u), H.get(v), 0);
G4.add(H.get(v), H.get(u), 0);
que[j * 2 + 1] = u;
que[j * 2 + 2] = v;
}
sort(que + 1, que + 2 * (k + 1) + 1, cmp);
c = unique(que + 1, que + 2 * (k + 1) + 1) - que - 1;
for (int j = 2; j <= c; j++) {
int r = lca(que[j], que[j - 1]);
if (r != que[j - 1] && r != que[j]) que[++c] = r;
}
sort(que + 1, que + c + 1, cmp);
c = unique(que + 1, que + c + 1) - que - 1;
for (int j = 1; j <= c; j++) val[H.get(que[j])] = sz[que[j]];
for (int j = 2; j <= c; j++) {
int r = lca(que[j], que[j - 1]), v = sum[p[que[j]][0]] - sum[r];
val[H.get(r)] = sz[r];
// cerr << "add : " << H.get(r) << " " << H.get(que[j]) << " " << v << "\n";
G3.add(H.get(r), H.get(que[j]), v);
G4.add(H.get(que[j]), H.get(r), v);
}
cout << solve(x, y) << "\n";
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端