题目描述
简要题意:
有一张
n
n
n 个节点,
m
m
m 条有向边的图。每条边都是由编号小的点指向编号大的点。有
Q
Q
Q 次询问,每次询问给出一个节点
T
T
T 和一个数字
Y
Y
Y,接着输入
Y
Y
Y 个数字
C
1
,
C
2
,
C
3
,
.
.
.
,
C
Y
C_1, C_2, C_3,...,C_Y
C1,C2,C3,...,CY,表示终点是
T
T
T,并且有
Y
Y
Y 个点不能作为起点,分别是
C
1
,
C
2
,
.
.
.
,
C
Y
C_1, C_2, ..., C_Y
C1,C2,...,CY。对每次询问输出 可以作为起点的所有点 到
T
T
T 的 最长路 的 最大值。
数据规模:
n
≤
1
0
5
n \leq 10^5
n≤105
m
≤
2
×
1
0
5
m \leq 2 \times 10^5
m≤2×105
Q
≤
1
0
5
Q \leq 10^5
Q≤105
1
≤
T
j
≤
n
,
0
≤
Y
j
≤
n
(
1
≤
j
≤
Q
)
1 \leq T_j \leq n,0 \leq Y_j \leq n(1 \leq j \leq Q)
1≤Tj≤n,0≤Yj≤n(1≤j≤Q)
∑
j
=
1
Q
Y
j
≤
1
0
5
\sum_{j = 1}^{Q} Y_j \leq10^5
∑j=1QYj≤105
分析:
我们发现对于每次询问,起点和终点都有可能不同。并且我们也不可能进行预处理每个点到其它点的最长路,这样复杂度太高。这里注意到一个关键数据 ∑ j = 1 Q Y j ≤ 1 0 5 \sum_{j = 1}^{Q} Y_j \leq 10^5 ∑j=1QYj≤105。也就是说每次被 ban 掉的点数量之和是 1 0 5 10^5 105 量级的。这就启示我们进行 根号分治。
设 S = ∑ j = 1 Q Y j S = \sum_{j = 1}^{Q} Y_j S=∑j=1QYj。
当 Y j ≥ S Y_j \geq \sqrt{S} Yj≥S 时,我们发现这样的情况不会超过 S \sqrt{S} S 次。由于每一条边都是由编号小的点指向编号大的点,因此图上一定没有环,并且拓扑序就是 1 − n 1 - n 1−n。那么我们暴力跑一遍拓扑排序,求出 d i s T j dis_{T_j} disTj 就好了。具体实现可以看代码,没做一次拓扑排序的复杂度是 O ( m ) O(m) O(m)。因此总复杂度是 O ( m S ) O(m\sqrt{S}) O(mS)。
当 Y j < S Y_j < \sqrt{S} Yj<S 时,这里有一个很妙的想法就是我们 对于每一个点预处理出来到它最长路最大的 S \sqrt{S} S 个点的编号和距离。那么对于一次询问,我们按距离从大到小枚举 T j T_j Tj 的 S \sqrt{S} S 个点,看那些点没有被 ban 掉,直接输出答案就好了。 预处理可以通过做一遍 拓扑排序 实现:我们对于每一个点开两个 vector 分别维护当前到它距离最大的 S \sqrt{S} S 个点的编号和距离。那么当点 u u u 向 v v v 转移时,我们可以做一遍类似归并排序的方式更新 v v v 的答案点集信息。每一次归并的复杂度是 O ( S ) O(\sqrt{S}) O(S),一共做 m m m 次,因此复杂度为 O ( m S ) O(m\sqrt{S}) O(mS)。
总复杂度 O ( m S ) O(m\sqrt{S}) O(mS)。
CODE:
#include<bits/stdc++.h>// 根号分治,有点强
#define pb push_back
using namespace std;// 当 yi > √n 时,直接跑拓扑,复杂度O(m)。当 yi < √n 时,预处理出到每个点最长路最长的 √n个点(拓扑时归并排序维护),直接输出答案。总复杂度O(m√n)
const int N = 1e5 + 10;
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
return x * f;
}
const int INF = 1e8;
int n, m, q, blo, c[N], lenth[N], T, Y, bok[N];
bool vis[N];
vector< int > E[N];
vector< int > V[N]; // V[i] 用来存对i而言最长路最大的 √n 个点的编号
vector< int > dis[N]; // dis[i] 用来存距离
void Merge(int x, int y) {
vector< int > tv, td;
int p = 0, q = 0, cnt = 0;
while(p < V[x].size() && q < V[y].size() && cnt < blo) {
if(dis[x][p] + 1 >= dis[y][q]) {
if(bok[V[x][p]] == -1) tv.pb(V[x][p]), td.pb(dis[x][p] + 1), bok[V[x][p]] = cnt ++, p ++ ;
else td[bok[V[x][p]]] = max(td[bok[V[x][p]]], dis[x][p] + 1), p ++ ;
}
else {
if(bok[V[y][q]] == -1) tv.pb(V[y][q]), td.pb(dis[y][q]), bok[V[y][q]] = cnt ++, q ++;
else td[bok[V[y][q]]] = max(td[bok[V[y][q]]], dis[y][q]), q ++;
}
}
if(cnt < blo) {
for(int i = p; i < V[x].size() && cnt < blo; i ++ ) {
if(bok[V[x][i]] == -1) tv.pb(V[x][i]), td.pb(dis[x][i] + 1), bok[V[x][i]] = cnt ++;
else td[bok[V[x][i]]] = max(td[bok[V[x][i]]], dis[x][i] + 1);
}
for(int i = q; i < V[y].size() && cnt < blo; i ++ ) {
if(bok[V[y][i]] == -1) tv.pb(V[y][i]), td.pb(dis[y][i]), bok[V[y][i]] = cnt ++;
else td[bok[V[y][i]]] = max(td[bok[V[y][i]]], dis[y][i]);
}
if(cnt < blo) {
if(bok[x] == -1) tv.pb(x), td.pb(1), bok[x] = cnt ++;
}
}
for(int i = 0; i < cnt; i ++ ) bok[tv[i]] = -1;
swap(tv, V[y]); swap(td, dis[y]);
}
void pre_work() {
for(int i = 1; i <= n; i ++ ) {
for(auto v : E[i]) {
Merge(i, v); // 合并,复杂度O(√n)
}
}
for(int i = 1; i <= n; i ++ ) {
if(V[i].size() < blo) V[i].pb(i), dis[i].pb(0);
}
}
int query(int t) {
for(int i = 1; i <= Y; i ++ ) vis[c[i]] = 1;
int res = -1;
for(int i = 0; i < V[t].size(); i ++ ) {
int x = V[t][i];
if(!vis[x]) {
res = dis[t][i];
break;
}
}
for(int i = 1; i <= Y; i ++ ) vis[c[i]] = 0;
return res;
}
int toopsort(int t) {
for(int i = 1; i <= n; i ++ ) lenth[i] = 0;
for(int i = 1; i <= Y; i ++ ) lenth[c[i]] = -INF;
for(int i = 1; i <= n; i ++ ) {
for(auto v : E[i])
lenth[v] = max(lenth[v], lenth[i] + 1);
}
return max(lenth[t], -1);
}
int main() {
freopen("party.in", "r", stdin);
freopen("party.out", "w", stdout);
memset(bok, -1, sizeof bok);
n = read(), m = read(), q = read();
blo = sqrt(n) + 1;
for(int i = 1; i <= m; i ++ ) {
int u = read(), v = read();
E[u].pb(v);
}
pre_work();
while(q -- ) {
T = read(), Y = read();
for(int i = 1; i <= Y; i ++ ) c[i] = read();
if(Y < blo) printf("%d\n", query(T));
else printf("%d\n", toopsort(T));
}
return 0;
}