https://ac.nowcoder.com/acm/contest/888/E
n个点m条双向边,每条边有通过的下界和上界size限制,主角要从1到n,可以在出发前改变一次size,问有多少种size可以从1到达n。边的上下界范围是1,1e9
第一次碰到这样的题,很有意思。
WA了一次是因为边界最大是n的两倍,我一开始线段树数组大小开成maxn<<2了,应该是(maxn*2)<<2,也就是maxn<<3.
1.首先把所有上下界进行区间离散化。
此处区间离散化的意思是:
假设有个区间[L,R],给R+1变成[L,R+1),对所有区间做此处理后,对边界进行离散化。离散化后的点i实际表示的范围是[b[i],b[i+1])。数组b是离散化后为i的值离散化前的值。
假设区间[L,R+1)离散化后为[X,Y),那么离散化后的区间[X,Y-1]实际上表示的区间为[b[X],b[Y]),也就是[L,R+1),也就是[L,R].
2.建线段树,将每一条边e的上下界范围内的结点打上这条边的标记,表示在这个结点的表示的上下界范围内,都是符合边e的上下界的,也就是在这个区域内,可以理解为这条边一直都存在。
3.从根开始往下搜,到达一个点u,把这个点上标记的边都连上(不用真的连,并查集并一下就可以了)。如果该点所有边连上之后, 1和n属于同一个连通块,就说明它们连通啦,也说明点u表示的区间范围内的点都是符合当前的上下界限制的,也就是都是可行的size。此时ans+=b[r+1]-b[l]就可以啦。(l,r是点u表示的区间~)
4.需要注意的是回溯的时候,我们需要拆掉一些边,所以并查集不可以路径压缩,启发式合并一下就好了。需要拆的时候恢复一下。比如点fu之前被并到fv上,进行的改变是fa[fu]=fv,sz[fv]+=sz[fu].恢复也就是sz[fa[fv]]-=sz[fv],fa[fv]=fv;(sz是集合的大小
还有一点要注意,如果并查集并的时候顺序为u1->u2->u3,那么拆的时候顺序要倒过来拆,因为前面u1并到u2的时候对u2是有sz贡献的,如果拆的时候先拆u1,u2,则u2,u3的时候u2的sz就不太对了。不过这个不会影响答案的正确性,会影响的是并查集的结构从而影响时间,好像不倒也不会错。
我的实现中vector c其实可以省掉的,所有的点的修改边都记在一个栈里面,每个节点记录一个该点加入的点个数之类的,回退的时候退记录的这个值这么多就可以了之类的,可能会快点吧。懒得思考写了个草率的咳咳咳。后来又补了一个精简还快的2333.
#include<bits/stdc++.h>
#define lson (t<<1)
#define rson (t<<1|1)
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
int n,m,tot,ans;
struct edge{
int u,v,l,r;
}e[maxm];
int a[maxm*2];
vector<int>v[maxn<<3],c[maxn<<3];//注意是<<3不是<<2
void build(int t,int l,int r){
if(l==r){v[t].clear();c[t].clear();return;}
int mid=l+r>>1;
build(lson,l,mid);build(rson,mid+1,r);
v[t].clear();c[t].clear();
}
void change(int t,int l,int r,int x,int y,int idx){
if(x<=l&&r<=y){
v[t].push_back(idx);//当前节点表示区域内,这条边存在
return;
}
int mid=l+r>>1;
if(x<=mid) change(lson,l,mid,x,y,idx);
if(y>mid) change(rson,mid+1,r,x,y,idx);
}
int fa[maxn],sz[maxn];
int ft(int u){return (fa[u]==u)?fa[u]:ft(fa[u]);}
void dfs(int t,int l,int r){
for(int i=0;i<(int)v[t].size();i++){
int u=e[v[t][i]].u,vv=e[v[t][i]].v;
int fu=ft(u),fv=ft(vv);
if(fu!=fv){
if(sz[fu]>=sz[fv]) fa[fv]=fu,sz[fu]+=sz[fv],c[t].push_back(fv);
else fa[fu]=fv,sz[fv]+=sz[fu],c[t].push_back(fu);
}
}
if(ft(1)==ft(n)){
ans+=a[r+1]-a[l];
}else if(l!=r){
int mid=l+r>>1;
dfs(lson,l,mid);dfs(rson,mid+1,r);
}
for(int i=(int)c[t].size()-1;i>=0;i--){//拆解的过程
int vv=c[t][i];
sz[fa[vv]]-=sz[vv];fa[vv]=vv;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].r);
a[++tot]=e[i].l;a[++tot]=e[i].r+1;
}
sort(a+1,a+tot+1);
int t=unique(a+1,a+tot+1)-a-1;
build(1,1,t);
for(int i=1;i<=m;i++)
change(1,1,t,lower_bound(a+1,a+t+1,e[i].l)-a,lower_bound(a+1,a+t+1,e[i].r+1)-a-1,i);//注意右边界是多了个-1的
dfs(1,1,t);
printf("%d\n",ans);
return 0;
}
#include<bits/stdc++.h>
#define lson (t<<1)
#define rson (t<<1|1)
using namespace std;
const int maxn=1e5+5;
int n,m,tot,ans;
struct edge{
int u,v,l,r;
}e[maxn];
int a[maxn*2],pos[maxn<<3],c[maxn<<3],top;
vector<int>v[maxn<<3];//注意是<<3不是<<2
void change(int t,int l,int r,int x,int y,int idx){
if(x<=l&&r<=y){
v[t].push_back(idx);//当前节点表示区域内,这条边存在
return;
}
int mid=l+r>>1;
if(x<=mid) change(lson,l,mid,x,y,idx);
if(y>mid) change(rson,mid+1,r,x,y,idx);
}
int fa[maxn],sz[maxn];
int ft(int u){return (fa[u]==u)?fa[u]:ft(fa[u]);}
void dfs(int t,int l,int r){
for(int i=0;i<(int)v[t].size();i++){
int u=e[v[t][i]].u,vv=e[v[t][i]].v;
int fu=ft(u),fv=ft(vv);
if(fu!=fv){
pos[t]++;
if(sz[fu]>=sz[fv]) fa[fv]=fu,sz[fu]+=sz[fv],c[top++]=fv;
else fa[fu]=fv,sz[fv]+=sz[fu],c[top++]=fu;
}
}
if(ft(1)==ft(n)){
ans+=a[r+1]-a[l];
}else if(l!=r){
int mid=l+r>>1;
dfs(lson,l,mid);dfs(rson,mid+1,r);
}
for(int i=top-1;i>=0&&pos[t];i--,pos[t]--){//拆解的过程
int vv=c[--top];
sz[fa[vv]]-=sz[vv];fa[vv]=vv;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].r);
a[++tot]=e[i].l;a[++tot]=e[i].r+1;
}
sort(a+1,a+tot+1);
int t=unique(a+1,a+tot+1)-a-1;
for(int i=1;i<=m;i++)
change(1,1,t,lower_bound(a+1,a+t+1,e[i].l)-a,lower_bound(a+1,a+t+1,e[i].r+1)-a-1,i);//注意右边界是多了个-1的
dfs(1,1,t);
printf("%d\n",ans);
return 0;
}