题目链接:点我啊╭(╯^╰)╮
题目大意:
n
n
n 个点,
m
m
m 条边
每条边有一个范围,编号在这个范围里的人能通过这条边
问最多有多少人能从
1
1
1 走到
n
n
n
解题思路:
从未见过的船新思路
考虑枚举区间
线段树的每个结点表示能覆盖当前区间的边的编号
然后暴力
d
f
s
dfs
dfs 整颗树,每到一个结点,将所有边的两点放到并查集里
注意从一个结点返回的时候,要回撤并查集
所以要用按秩合并,如果
1
1
1 和
n
n
n 在一个集里,则加入到答案里
时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
核心:线段树 + 可撤销并查集维护图
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const ll mod = 998244353;
const int maxn = 2e5 + 10;
int n, m, cnt = 1, ls[maxn<<2];
int f[maxn], siz[maxn], ans;
vector <int> t[maxn<<2];
struct node{
int u, v, l, r;
} a[maxn];
int getf(int x){
return x==f[x] ? x : getf(f[x]);
}
void update(int L, int R, int c, int l, int r, int rt){
if(L<=l && r<=R){
t[rt].push_back(c);
return;
}
int m = l + r >> 1;
if(L <= m) update(L, R, c, l, m, rt<<1);
if(R > m) update(L, R, c, m+1, r, rt<<1|1);
}
void dfs(int l, int r, int rt){
vector <pii> lastf;
for(int i=0; i<t[rt].size(); i++){
int x = a[t[rt][i]].u, y = a[t[rt][i]].v;
x = getf(x), y = getf(y);
if(x == y) continue;
if(siz[x] > siz[y]) swap(x, y);
f[x] = y; int d = 0; // 按秩合并
if(siz[x] == siz[y]) siz[y]++, d++;
lastf.push_back({x, d});
}
int m = l + r >> 1;
if(getf(1)==getf(n)) ans += ls[r+1] - ls[l];
else if(l < r) dfs(l,m,rt<<1), dfs(m+1,r,rt<<1|1);
for(int i=lastf.size()-1; ~i; i--){
siz[f[lastf[i].first]] -= lastf[i].second;
f[lastf[i].first] = lastf[i].first;
}
lastf.clear();
}
int main() {
scanf("%d%d", &n, &m);
for(int i=0; i<=n; i++) f[i] = i, siz[i] = 1;
for(int i=0; i<m; i++){
scanf("%d%d", &a[i].u, &a[i].v);
scanf("%d%d", &a[i].l, &a[i].r);
ls[cnt++] = a[i].l;
ls[cnt++] = a[i].r + 1;
}
sort(ls+1, ls+cnt);
cnt = unique(ls+1, ls+cnt) - ls - 1;
for(int i=0; i<m; i++){
update(lower_bound(ls+1,ls+cnt+1,a[i].l)-ls, \
lower_bound(ls+1, ls+cnt+1, a[i].r+1)-ls-1, i, 1, cnt, 1);
}
dfs(1, cnt, 1);
printf("%d\n", ans);
}