20241020比賽總結

wangsiqi2010916發表於2024-10-20

T1 Reverse

https://www.gxyzoj.com/d/hzoj/p/P980

假設1在點i時,這個1可以透過一次翻轉到達那些點,將這些點和i連邊,此時答案就是s到x的最短路

但是,此時邊數也會到達\(n^2\)級別

考慮最佳化,因為邊權均為1,所以可以直接bfs,可以發現每個點能轉移的點的奇偶性是有限制的,而且每個點至多被更新一次

所以可以將所有點按奇偶性分類後丟進set,然後找到轉移邊界,暴力更新即可

注意,選出的子串的長度必須為k,所以在靠近兩個端點的地方是不能取點作為中心的

程式碼:

#include<cstdio>
#include<set>
#include<queue>
#define it set<int>::iterator
using namespace std;
int n,m,s,k,b[100005],ans[100005];
double lmid,rmid;
set<int> s1,s2;
queue<int> q;
void bfs(int st)
{
	for(int i=1;i<=n;i++)
	{
		ans[i]=1e9;
	}
	ans[st]=0;
	q.push(st);
	if(st%2) s1.erase(st);
	else s2.erase(st);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		double ls=u-lmid,rs=rmid-u;
		int l=lmid-ls,r=rmid+rs;
		l=max(l,max(0,u-k+1)),r=min(r,min(n,u+k-1));
	//	printf("%d %d %d %d\n",u,l,r,ans[u]);
		if((u+k+1)%2)
		{
			it lid=s1.lower_bound(l);
			it rid=s1.lower_bound(r);
			while(*lid<=r&&lid!=s1.end())
			{
				if(b[*lid])
				{
					lid++;
					continue;
				}
				ans[*lid]=ans[u]+1;
				q.push(*lid);
				lid++;
			}
		//	printf("1");
			lid=s1.lower_bound(l);
			if(*rid<=r&&rid!=s1.end()) rid++;
			s1.erase(lid,rid);
		}
		else
		{
			it lid=s2.lower_bound(l);
			it rid=s2.lower_bound(r);
			while(*lid<=r&&lid!=s2.end())
			{
				if(b[*lid])
				{
					lid++;
					continue;
				}
				ans[*lid]=ans[u]+1;
				q.push(*lid);
				lid++;
			}
			lid=s2.lower_bound(l);
			if(*rid<=r&&rid!=s2.end()) rid++;
			s2.erase(lid,rid);
		}
	}
}
int main()
{
	freopen("reverse.in","r",stdin);
	freopen("reverse.out","w",stdout);
	scanf("%d%d%d%d",&n,&k,&m,&s);
	lmid=(1+k)*1.0/2.0,rmid=(n+n-k+1)*1.0/2.0;
	for(int i=1;i<=m;i++)
	{
		int x;
		scanf("%d",&x);
		b[x]=1;
	}
	for(int i=1;i<=n;i++)
	{
		if(i%2) s1.insert(i);
		else s2.insert(i);
	}
	bfs(s);
	for(int i=1;i<=n;i++)
	{
		if(ans[i]!=1e9) printf("%d ",ans[i]);
		else printf("-1 ");
	}
	return 0;
}