JOISC2018h 比太郎的聚会(根号分治,拓扑排序)

题目描述

在这里插入图片描述

简要题意:
       有一张 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 n105
       m ≤ 2 × 1 0 5 m \leq 2 \times 10^5 m2×105
       Q ≤ 1 0 5 Q \leq 10^5 Q105
       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) 1Tjn0Yjn1jQ
       ∑ j = 1 Q Y j ≤ 1 0 5 \sum_{j = 1}^{Q} Y_j \leq10^5 j=1QYj105

分析:

       我们发现对于每次询问,起点和终点都有可能不同。并且我们也不可能进行预处理每个点到其它点的最长路,这样复杂度太高。这里注意到一个关键数据 ∑ j = 1 Q Y j ≤ 1 0 5 \sum_{j = 1}^{Q} Y_j \leq 10^5 j=1QYj105。也就是说每次被 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} YjS 时,我们发现这样的情况不会超过 S \sqrt{S} S 次。由于每一条边都是由编号小的点指向编号大的点,因此图上一定没有环,并且拓扑序就是 1 − n 1 - n 1n。那么我们暴力跑一遍拓扑排序,求出 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; 
}
  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值