机房测试5:reverse(bfs+set 或 线段树优化建图)
Posted mowanying
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机房测试5:reverse(bfs+set 或 线段树优化建图)相关的知识,希望对你有一定的参考价值。
题目:
分析:
首先画样例分析一下,会发现如果要求一个位置要多少次翻转,就将这个位置向与它关联的点连边(关联点指的是可能与它值互换的位置),一直连到起点为止,连边的次数即为它所需步数。
所以转换成求单源最短路,因为边权为1,可以用bfs。
但是这道题n的范围很大,刚刚的做法是n*k的,考虑优化。
法1:在建图上优化
题目要求的是区间翻转,所以也对应着相关性质:每个点连边一定是都连的奇数点或偶数点(画图可知),且这些奇数偶数点都对应着一段连续的区间。
如果可以将点向点连边优化成点向区间连边,复杂度就可以大大减小。
怎么连呢?
用两颗线段树维护奇数点集和偶数点集,每次连一段区间的时候转换成与线段树中区间对应的点连边,保证了每个点最多连log条边。
然后再bfs即可。
法2:在bfs中优化
我们其实可以不连边,直接从s点开始bfs,更新每一个第一次被遍历点的操作次数。
每一次取出一个点,在遍历与其相关的点的时候,在x-k+1~x+k-1这个范围内找满足条件的数。
找的时候花费了k的复杂度,但有些已经被更新过的点是没有必要再访问一次的
怎么优化呢?(明显是不能vis打标记的,因为我们for的时候还是会访问到它)
通过用set记录当前区间中未访问到的点有哪些,每次只for这些点即可。访问后在set中erase。
优化后复杂度O(n*logn)(set删除元素,加入元素自带logn)
注意代码细节:
1.当x-k+1<1了,区间就不再是1~x+k-1了,要重新计算新的l’(注意不能把1当做l,因为本应该翻转的区间取不到1)
2.set的erase只能这样打:se[...].erase(it++)
it++不能放在外面!!!(神奇的stl)
#include<bits/stdc++.h> using namespace std; #define ri register int int dis[100005],vis[100005],pos[100005]; int n,k,m,s,a; queue<int>q; set<int> se[2]; set<int> :: iterator it; int read() int x=0,fl=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) if(ch==‘-‘) fl=-1; ch=getchar(); while(ch<=‘9‘&&ch>=‘0‘) x=x*10+ch-‘0‘,ch=getchar(); return fl*x; int main() freopen("reverse.in","r",stdin); freopen("reverse.out","w",stdout); n=read(); k=read(); m=read(); s=read(); for(ri i=1;i<=m;++i) a=read(),pos[a]=1; for(ri i=1;i<=n;++i) if(!pos[i] && i!=s) se[i&1].insert(i); memset(dis,-1,sizeof(dis)); q.push(s); dis[s]=0; while(!q.empty()) int u=q.front(); q.pop(); int l=u-k+1,r=u+k-1,op=(k&1)^(u&1)^1;//判断连边的奇偶:如果同为奇数或同为偶数 就走奇数点 反则走偶数点 //画线段推一下公式 处理边界超出后 它实际能够走到的点 if(l<1) l=k-u+1; if(r>n) r=2*n+1-u-k; it=se[op].lower_bound(l); while(it!=se[op].end() && *it<=r) dis[*it]=dis[u]+1; q.push(*it); se[op].erase(it++);//保证了每一个点只能走一次 for(int i=1;i<=n;++i) printf("%d ",dis[i]); /* 10 4 3 3 2 5 10 */
以上是关于机房测试5:reverse(bfs+set 或 线段树优化建图)的主要内容,如果未能解决你的问题,请参考以下文章
csp-s模拟测试59(10.4)「Reverse」(set)·「Silhouette」(容斥)