Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Think per color, not per query. A color contributes to a query exactly when every node on the tree path has that color.
For one fixed color , look at the induced subgraph of nodes whose set contains . Since the original graph is a tree, contributes to iff and are in the same connected component of this induced subgraph.
Small color classes can be handled by brute force over their colored nodes: if color appears on nodes, then it only affects pairs inside components totaling at most ordered pairs. Use a hash map from ordered node pair to query indices/counts.
Large color classes need a different trick. There can only be about of them if “large” means frequency . For each large color, build its connected components once and answer all queries by checking component ids.
Pick . For a light color, DFS only among its marked nodes to find colored components, then enumerate all ordered pairs inside each component and add to matching queries. For a heavy color, mark its nodes, compute component ids over the whole tree in linear time, scan all queries, then unmark. Total work is about .
The annoying-looking intersection is hiding a much nicer graph question.
For a color , define as the set of nodes containing . A query path contains only color- nodes iff every node on that path lies in .
Because the base graph is a tree, that is exactly the same as:
and are in the same connected component of the subgraph induced by .
So every color defines some connected components, and each query asks: how many colors put these two endpoints in the same component?
Now we need to do that without doing something stupid like checking all pairs. That would be very 2800-to-WA pipeline behavior.
Split colors by frequency
Let . Total frequency over all colors is .
Pick a threshold .
A color is:
There are at most heavy colors.
We handle these two cases differently.
Preprocess the queries
For light colors, we want to quickly know whether a pair of nodes is actually one of the asked queries.
Store all query indices in a hash map keyed by ordered pair , and also store both directions. If the input query is , insert index under keys and . For , insert only once.
Then later, if a color component contains nodes , we can ask: “are there any queries with endpoints ?” and add to all those answers.
Duplicates are fine: if the same query pair appears multiple times, the map stores all those query indices.
Light colors
For a light color , there are at most marked nodes.
We need the connected components induced by those marked nodes. Since the original graph is a tree, we can just start DFS/BFS from each marked node and only walk to neighbors also marked with this color.
For every component with nodes , color contributes to every query whose two endpoints both lie in that component.
So enumerate all ordered pairs inside the component and look them up in the query map. If present, add to those query answers.
The cost for this color is roughly:
Summed over all light colors, that is .
Heavy colors
For a heavy color, enumerating all pairs inside a giant component is exactly how you summon TLE.
But there are few heavy colors: at most .
For each heavy color :
Computing components can be done in by scanning nodes and DFSing through marked nodes. Then scanning all queries costs .
Total heavy cost:
With and global sums bounded by , this is easily fine.
Why the component test is correct
Fix a color .
If appears on every node of the path , then this whole path is inside , so and are connected in the induced subgraph.
If and are connected inside the induced subgraph, the connecting path between them inside a tree must be the unique original tree path. Therefore every node on is in .
So same induced component is not just a useful condition; it is exactly the query condition.
Complexity
Let .
Light colors cost pair enumeration.
Heavy colors cost:
Memory is , plus the query hash map.
This fits comfortably under the limits.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct PairHash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
static inline uint64_t keyPair(int a, int b) {
return (uint64_t(a) << 32) | uint32_t(b);
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, k, s, q;
cin >> n >> k >> s >> q;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<vector<int>> byColor(k + 1);
for (int i = 0; i < s; i++) {
int v, x;
cin >> v >> x;
byColor[x].push_back(v);
}
vector<pair<int, int>> queries(q);
unordered_map<uint64_t, vector<int>, PairHash> ask;
ask.reserve(size_t(2 * q + 10));
ask.max_load_factor(0.7);
for (int i = 0; i < q; i++) {
int u, v;
cin >> u >> v;
queries[i] = {u, v};
ask[keyPair(u, v)].push_back(i);
if (u != v) ask[keyPair(v, u)].push_back(i);
}
int B = max(1, int(sqrt(s)) + 1);
vector<int> ans(q, 0);
vector<int> mark(n + 1, 0), comp(n + 1, 0), seen(n + 1, 0);
int tag = 0, seenTag = 0;
vector<int> st;
for (int color = 1; color <= k; color++) {
auto &nodes = byColor[color];
if (nodes.empty()) continue;
++tag;
for (int v : nodes) mark[v] = tag;
if ((int)nodes.size() <= B) {
++seenTag;
for (int start : nodes) {
if (seen[start] == seenTag) continue;
vector<int> cur;
st.clear();
st.push_back(start);
seen[start] = seenTag;
while (!st.empty()) {
int u = st.back();
st.pop_back();
cur.push_back(u);
for (int v : g[u]) {
if (mark[v] == tag && seen[v] != seenTag) {
seen[v] = seenTag;
st.push_back(v);
}
}
}
for (int a : cur) {
for (int b : cur) {
auto it = ask.find(keyPair(a, b));
if (it == ask.end()) continue;
for (int id : it->second) ans[id]++;
}
}
}
} else {
int cid = 0;
for (int v : nodes) comp[v] = 0;
for (int start : nodes) {
if (comp[start]) continue;
++cid;
st.clear();
st.push_back(start);
comp[start] = cid;
while (!st.empty()) {
int u = st.back();
st.pop_back();
for (int v : g[u]) {
if (mark[v] == tag && comp[v] == 0) {
comp[v] = cid;
st.push_back(v);
}
}
}
}
for (int i = 0; i < q; i++) {
auto [u, v] = queries[i];
if (mark[u] == tag && mark[v] == tag && comp[u] == comp[v]) ans[i]++;
}
}
}
for (int i = 0; i < q; i++) cout << ans[i] << ' ';
cout << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct PairHash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
static inline uint64_t keyPair(int a, int b) {
return (uint64_t(a) << 32) | uint32_t(b);
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n, k, s, q;
cin >> n >> k >> s >> q;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<vector<int>> byColor(k + 1);
for (int i = 0; i < s; i++) {
int v, x;
cin >> v >> x;
byColor[x].push_back(v);
}
vector<pair<int, int>> queries(q);
unordered_map<uint64_t, vector<int>, PairHash> ask;
ask.reserve(size_t(2 * q + 10));
ask.max_load_factor(0.7);
for (int i = 0; i < q; i++) {
int u, v;
cin >> u >> v;
queries[i] = {u, v};
ask[keyPair(u, v)].push_back(i);
if (u != v) ask[keyPair(v, u)].push_back(i);
}
int B = max(1, int(sqrt(s)) + 1);
vector<int> ans(q, 0);
vector<int> mark(n + 1, 0), comp(n + 1, 0), seen(n + 1, 0);
int tag = 0, seenTag = 0;
vector<int> st;
for (int color = 1; color <= k; color++) {
auto &nodes = byColor[color];
if (nodes.empty()) continue;
++tag;
for (int v : nodes) mark[v] = tag;
if ((int)nodes.size() <= B) {
++seenTag;
for (int start : nodes) {
if (seen[start] == seenTag) continue;
vector<int> cur;
st.clear();
st.push_back(start);
seen[start] = seenTag;
while (!st.empty()) {
int u = st.back();
st.pop_back();
cur.push_back(u);
for (int v : g[u]) {
if (mark[v] == tag && seen[v] != seenTag) {
seen[v] = seenTag;
st.push_back(v);
}
}
}
for (int a : cur) {
for (int b : cur) {
auto it = ask.find(keyPair(a, b));
if (it == ask.end()) continue;
for (int id : it->second) ans[id]++;
}
}
}
} else {
int cid = 0;
for (int v : nodes) comp[v] = 0;
for (int start : nodes) {
if (comp[start]) continue;
++cid;
st.clear();
st.push_back(start);
comp[start] = cid;
while (!st.empty()) {
int u = st.back();
st.pop_back();
for (int v : g[u]) {
if (mark[v] == tag && comp[v] == 0) {
comp[v] = cid;
st.push_back(v);
}
}
}
}
for (int i = 0; i < q; i++) {
auto [u, v] = queries[i];
if (mark[u] == tag && mark[v] == tag && comp[u] == comp[v]) ans[i]++;
}
}
}
for (int i = 0; i < q; i++) cout << ans[i] << ' ';
cout << '\n';
}
return 0;
}