abc Reachable Set
AT_abc401_e [ABC401E] Reachable Set
洛谷题目传送门
atcoder题目传送门
题目描述
给定一个包含 NNN 个顶点和 MMM 条边的无向图。顶点编号为 1,2,…,N1,2,\ldots,N1,2,…,N,第 iii 条边 (1≤i≤M)(1 \leq i \leq M)(1≤i≤M) 连接顶点 uiu_iui 和顶点 viv_ivi。
对于每个 k=1,2,…,Nk=1,2,\ldots,Nk=1,2,…,N,请解决以下问题:
考虑以下操作:
- 选择一个顶点,删除该顶点及其所有连接的边。
通过重复上述操作,判断是否能够满足以下条件:
- 从顶点 111 出发,通过边能够到达的顶点集合恰好为 {1,2,…,k}\{1,2,\ldots,k\}{1,2,…,k}(共 kkk 个顶点)。
如果可能,求出满足条件所需的最少操作次数;否则输出
-1
。
输入格式
输入通过标准输入给出,格式如下:
NNN MMM
u1u_1u1 v1v_1v1
u2u_2u2 v2v_2v2
⋮\vdots⋮
uMu_MuM vMv_MvM
输出格式
输出 NNN 行。第 iii 行 (1≤i≤N)(1 \leq i \leq N)(1≤i≤N) 输出 k=ik=ik=i 时的答案:若无法满足条件则输出 -1
,否则输出所需的最少操作次数。
输入输出样例 #1
输入 #1
6 7
1 2
1 5
2 3
2 4
2 5
3 6
5 6
输出 #1
2
3
3
2
1
0
输入输出样例 #2
输入 #2
5 4
1 5
2 3
3 4
4 5
输出 #2
1
-1
-1
-1
0
输入输出样例 #3
输入 #3
2 0
输出 #3
0
-1
输入输出样例 #4
输入 #4
11 25
6 9
5 9
2 3
1 9
10 11
4 5
9 10
8 9
7 8
3 5
1 7
6 10
4 7
7 9
1 10
4 11
3 8
2 7
3 4
1 8
2 8
3 7
2 10
1 6
6 11
输出 #4
5
-1
-1
-1
-1
-1
4
3
2
1
0
说明/提示
约束条件
- 1≤N≤2×1051 \leq N \leq 2 \times 10^51≤N≤2×105
- 0≤M≤3×1050 \leq M \leq 3 \times 10^50≤M≤3×105
- 1≤ui<vi≤N1 \leq u_i < v_i \leq N1≤ui<vi≤N (1≤i≤M)(1 \leq i \leq M)(1≤i≤M)
- (ui,vi)≠(uj,vj)(u_i, v_i) \neq (u_j, v_j)(ui,vi)=(uj,vj) (1≤i<j≤M)(1 \leq i < j \leq M)(1≤i<j≤M)
- 输入的所有数值均为整数
样例解释 1
例如,当 k=2k=2k=2 时,可以通过删除顶点 333、顶点 444 和顶点 555(共 3 次操作),使得从顶点 111 可达的顶点仅为 {1,2}\{1,2\}{1,2}。由于无法通过少于 3 次操作满足条件,因此第 2 行输出 3
。
当 k=6k=6k=6 时,不需要删除任何顶点即可满足条件,因此第 6 行输出 0
。
样例解释 3
图中可能不存在任何边。
思路详解
确定算法
我们先思考如何快速判断与1连通的连通块只包含1−k1-k1−k的节点呢?连通块大小siz==ksiz==ksiz==k?光这个条件还不够,我们还要添加一个条件。所有的节点编号都小于kkk?那现在可以了。求连通块和连通块大小可以使用并查集。每次枚举kkk,加入相连的节点都小于kkk的边。判断1所在连通块大小即可。
实现方法
思考如何每次加入要加的边。我们可以用一个邻接表eie_{i}ei记录每一个连接了iii的点。当我们枚举k==ik==ik==i时,我们就判断与iii相连的节点jjj是否满足j<=kj<=kj<=k,如果是则直接合并。不是则删除。但是我们还是需要考虑一下如何删除?
我们发现当前jjj不是不代表以后jjj不是,我们可以使用一个setsetset记录删除了哪些节点。需要删除了则加入setsetset。当k==jk==jk==j时,就从setsetset中删除jjj。每次删除的数量即为set.size()set.size()set.size()。
code
#include<bits/stdc++.h>
using namespace std;
const int N=6e5+5;
int n,m;
int siz[N],fa[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void unio(int x,int y){int f1=find(x),f2=find(y);if(f1!=f2){fa[f1]=f2;siz[f2]+=siz[f1];//合并记得更新siz}
}
vector<int>a[N];
int o=0;
set<int>se;//保存删除了的点
int main(){
//首先考虑如何保证与1可到达的恰好为1-k
//若与1相连的恰好只有k个且删去了所以大于k的边肯定可以
//但是我们不用删去大于k的所有点,只需去除那些会和不大于k的节点相连的即可
//由于要记录一个连通块内的点的个数,我们想到了并查集
//对于一开始删去的点,我们后面可能将其加入回去,所以我们要将边给每个节点复制一次,以便后来可以再次访问到这条边
//我们要记录删去的点的数量,又可能将一些点加入或移除,考虑直接使用setcin>>n>>m;for(int i=1;i<=n;i++)fa[i]=i,siz[i]=1;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;a[x].push_back(y);a[y].push_back(x);}for(int i=1;i<=n;i++){if(se.count(i))se.erase(i);//需要当前节点,将其移除for(int j:a[i]){if(j<=i){//小于i就加入unio(i,j);}else{se.insert(j);}//否则这个节点需要删除}if(siz[find(i)]!=i)cout<<-1<<'\n';//判断是否能够恰好到达i个节点else cout<<se.size()<<'\n';//能则输出需要删除的点数}return 0;
}