题目:

 

 

 

 分析:

首先画样例分析一下,会发现如果要求一个位置要多少次翻转,就将这个位置向与它关联的点连边(关联点指的是可能与它值互换的位置),一直连到起点为止,连边的次数即为它所需步数。

所以转换成求单源最短路,因为边权为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
*/
View Code

 

posted on 2019-10-05 19:31  rua-rua-rua  阅读(272)  评论(0编辑  收藏  举报