Hetao P4065 病毒 题解 [ 蓝 ] [ 图论建模 ] [ BFS ] [ 并查集 ]
病毒:第一次把 std 爆标了,开心。


注意到只有一个位置是开启的,因此一共只有 \(n\) 种合法状态。
于是考虑图论建模,把开启位置的每一种情况看做一个节点。观察翻转操作,不难发现连边有下面两种方式:
- \(k\) 为奇数时,对与所有在范围内的与 \(k\) 同奇偶性的位置连边。
- \(k\) 为偶数时,对与所有在范围内的与 \(k\) 不同奇偶性的位置连边。
无脑线段树优化建图是可做的,时间复杂度 \(O(n\log n)\)。进一步考虑如何线性解决这个问题。
因为 BFS 的特殊性质,所以从 \(u\) 只会走到本该连边的点中之前未被访问过的点,其他点都没有必要访问。于是考虑能否使用均摊算法使得每次访问都只会访问到之前未被访问的点。
std 给出的做法是用 set 维护奇偶位置上未被访问的点,每次在 set 中选一个最小的满足要求的点访问,然后从 set 里删掉。但实际上可以利用并查集进一步优化,利用并查集维护序列连通性的 trick 维护每个点后面第一个未被访问过的点,即可做到线性复杂度。
时间复杂度 \(O(n)\),对于屏蔽掉的位置 \(x\) 在并查集上直接指向 \(x+2\) 即可,因为奇偶是分开跳的。同时注意翻转区间越界的判断。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=100005;
int n,k,m,x;
int f[N],dis[N];
void init()
{
for(int i=1;i<=n+2;i++)f[i]=i;
}
int findf(int x)
{
if(f[x]!=x)f[x]=findf(f[x]);
return f[x];
}
void combine(int x,int y)
{
int fx=findf(x),fy=findf(y);
f[fx]=fy;
}
void BFS()
{
memset(dis,-1,sizeof(dis));
queue<int>q;
q.push(x);
dis[x]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
int p=u-k+1;
if(p<0)p=k-u+1;
while(1)
{
int v=findf(p);
if(v<=u+k-1&&v<=2*n+1-u-k&&dis[v]==-1)
{
q.push(v);
dis[v]=dis[u]+1;
}
else break;
combine(v,v+2);
p=findf(p);
}
}
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>k>>m>>x;
init();
combine(x,x+2);
for(int i=1;i<=m;i++)
{
int p;
cin>>p;
combine(p,p+2);
}
BFS();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}

浙公网安备 33010602011771号