牛客多校第八场 E - Explorer(时间分治 + 离散化 + 并查集 + 线段树)
巧妙的时间分治思想
根据通道的大小的大小建立一棵线段树。
对于每条边,根据它 l 和 r的大小把它放到线段树对应的位置。
然后从根开始dfs,每经过一个结点,就把结点存的边都加进来,通过并查集维护,跑到叶子结点的时候,判断一下1和n在不在同一个并查集中,在的话表明叶子节点所代表的大小可以使得主角从1抵达n,把它记录下来。合并并查集的时候按秩合并
观察题目给的数据范围可知n和m都在1e5里面,所以可以把边的 l 和 r 的范围离散到1e5左右。
注意把 边的大小 离散化到线段树上时开闭区间问题,代码里是 点 对应 点,是两端都是闭
//建立的线段树,叶子节点的值对应 离散化前 通道大小的端点的值
//比如离散化前,通道大小为17 19,离散化后为 3 4,则3 对应 17,4对应19,两端都是闭
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
struct node{
int u,v,l,r,ml,mr;
}edge[MAXN];
struct node1{
int value,num,v;
}value[MAXN<<2];
bool cmp(node1 x,node1 y){return x.value < y.value;}
bool cmp1(node1 x,node1 y){return x.num<y.num;}
struct node3{
node3(int a = 0,int b = 0):u(a),v(b){}
int u,v;
};
struct node2{
int l,r;
vector<node3> q;
int mid(){return (r - l) / 2 + l;}
}tree[MAXN<<4];
void build(int st,int ed,int x)
{
tree[x].l = st;
tree[x].r = ed;
if (st == ed) return;
build(st,tree[x].mid(),x * 2);
build(tree[x].mid()+1,ed,x * 2 + 1);
}
void modify(int st,int ed,int x,node3 t)
{
if (tree[x].l >= st && tree[x].r <= ed)
{
tree[x].q.push_back(t);
return;
}
int mid = tree[x].mid();
if (mid >= st) modify(st,ed,x * 2,t);
if (ed > mid) modify(st,ed,x*2+1,t);
}
int f[MAXN],n,m,len = 1,size[MAXN];
node3 ans[MAXN];
int find(int x)
{
while (x != f[x]) x = f[x];
return x;
}
struct save{
save(int c = 0,int d = 0,int e = 0,int f = 0):fx(c),fy(d),sizefx(e),sizefy(f){}
int fx,fy,sizefx,sizefy;
};
void dfs(int u)
{
vector<save> s;
for (int i = 0;i < tree[u].q.size();i++)//枚举当前结点存的边
{
int x = tree[u].q[i].u,y = tree[u].q[i].v;
int fx = find(x),fy = find(y);
s.push_back(save(fx,fy,size[fx],size[fy]));//记录下来,用于回退
if (fx == fy) continue;
if (size[fx] < size[fy]) f[fx] = fy;//并查集按秩合并
else
{
f[fy] = fx;
if (size[fx] == size[fy]) size[fx]++;
}
}
if (tree[u].l == tree[u].r)//如果到了叶子节点
{
int f1 = find(1),fn = find(n);
if (f1 == fn)
{
if (ans[len].u == 0) ans[len].v = ans[len].u = tree[u].l;//记录答案左端点
else ans[len].v = tree[u].l;//记录答案右端点
}
else if (ans[len].v != 0) len++;
}
if (tree[u].l != tree[u].r)
{
dfs(u * 2);
dfs(u * 2 + 1);
}
for (int i = s.size() - 1;i>=0;i--)//回退
{
save t = s[i];
int fx = t.fx,fy = t.fy;
size[fx] = t.sizefx,size[fy] = t.sizefy,f[fx] = fx,f[fy] = fy;
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1;i<=n;i++) f[i] = i,size[i] = 1;//并查集初始化,size表示并查集的秩
int lenv = 1;//value用来离散化
for (int i = 1;i<=m;i++)
{
scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].l,&edge[i].r);
value[value[lenv].num = lenv].value = edge[i].l - 1; lenv++;
value[value[lenv].num = lenv].value = edge[i].l; lenv++;
value[value[lenv].num = lenv].value = edge[i].r; lenv++;
value[value[lenv].num = lenv].value = edge[i].r + 1; lenv++;
//防止相邻的两个区间都可以从1到n,而两个区间之间的值不可以时,把两个区间直接合并起来
}
//离散化
lenv--;
sort(value+1,value+lenv+1,cmp);
int count = 0;
for (int i = 1;i<=lenv;i++)
{
if (value[i].value != value[i - 1].value) value[i].v = ++count;
else value[i].v = count;
}
sort(value+1,value+lenv+1,cmp1);
for (int i = 1;i<=lenv;i++)
{
if (i % 4 == 2) edge[i / 4 + 1].ml = value[i].v;
else if (i % 4 == 3) edge[i / 4 + 1].mr = value[i].v;
}
build(1,count,1);
for (int i = 1;i<=m;i++) modify(edge[i].ml,edge[i].mr,1,node3(edge[i].u,edge[i].v));//把边添加到线段树中
dfs(1);//从根节点开始跑dfs枚举大小寻找答案
len--;
int sum = 0;
sort(value+1,value+lenv+1,cmp);
for (int i = 1,j = 1,last;i<=lenv && j<=len;i++)//把线段树上的大小映射回原来得到大小计算答案
{
if (value[i].v == ans[j].u) last = value[i].value;
if (value[i].v == ans[j].v) sum += value[i].value - last + 1,j++;
}
cout<<sum;
return 0;
}
本文来自博客园,作者:Un-Defined,转载请保留本文署名Un-Defined,并在文章顶部注明原文链接:https://www.cnblogs.com/EIPsilly/p/17463697.html