P3452 [POI2007] BIU-Offices 题解

· · 题解

从直接搜索的角度优化。

题意

n 个点的完全图,给定 m 条边不能走,问连通块个数。

朴素

从朴素做法来想,直接广搜。

想其更新新点的流程(以下简称“流程”),是对于出队的点 u,找到它所有连边,如果连边对应的点没有被访问过,即没被 vis 数组标记,那么就将它进队。

复杂度 O(n+e)n 是点数,e 是边数。

过不了,只因这个题 en^2 同阶的特点。

优化

上面提到的流程是我们复杂度的瓶颈,想想在这个题下我们如何优化。

输入的边,代表在图中删掉了这条边。

因为大部分边都能走,就相当于 u 除了删掉的边对应的点外,其余 v=1 \to n 都可直达。

由于不能直达的点相对很少, 流程就变成了对于 u,扫一遍(除了被删的点,剩余的)整个 vis 数组,找到 vis 没有被标记的点,进入队列。

似乎很好优化了?用链表或并查集跳过 vis 标记过的点,不扫它们不就行了,均摊下来就 O(n) 了。

细节在于,如何处理扫 vis 数组时跳过被删的点?我们可以将每个点 u 预处理出可达区间,区间之间不包含的部分就是相对 u 而言删掉的点,也就是 u 出队时扫 vis 跳过的点。处理好这个,流程中,我们对每个区间分别做即可。

举个例子

n=100

若加入 $u=1,v=5$,对 $1$ 号来说就是删掉了 $5$ 号,$1$ 号的可达区间变为 $[2,4],[6,100]$。 若再加入 $u=1,v=20$,$1$ 号的可达区间变为 $[2,4],[6,19],[21,100]$。 以此类推。注意上面只是拿 $1$ 号点举例。处理时要对每个点处理,比如对 $3$ 而言第一个读入就是删掉了 $1$。 这部分不是重点,但是可能有点抽象,可以直接读代码帮助理解。 ```cpp #define pr make_pair int fa[N],c[N]; int getfa(int x){if(x>n)return x;return fa[x]==x?x:fa[x]=getfa(fa[x]);} vector<int>G[N];//暂时存储删掉的边 vector<pair<int,int> >v[N]; int bfs(int x) { int ret=0; queue<int>q; q.push(x); fa[x]=x+1; while(q.size()) { int nq=q.front(); q.pop(); ret++; for(auto rg:v[nq])//对每个可达区间分别做 { int l=rg.first,r=rg.second; for(int i=getfa(l);i<=r;i=getfa(i)) q.push(i),fa[i]=i+1; } } return ret;//返回连通块大小 } inline void solve(){ cin>>n>>m; for(int i=1;i<=n;i++)fa[i]=i,G[i].push_back(i); for(int i=1,u,v;i<=m;i++) cin>>u>>v,G[u].push_back(v),G[v].push_back(u); for(int i=1;i<=n;i++)//处理删边 { sort(G[i].begin(),G[i].end()); int l=1; for(auto cc:G[i]) { if(l<=cc-1) v[i].push_back(pr(l,cc-1)); l=cc+1; } if(l<=n) v[i].push_back(pr(l,n)); } vector<int>ans; for(int i=1;i<=n;i=getfa(i)) ans.push_back(bfs(i)); cout<<ans.size()<<endl; sort(ans.begin(),ans.end()); for(auto p:ans) cout<<p<<' '; } ```