CF1661B Getting Zero

Link\text{Link}

题意

对于一个整数 v(0v<32768)v(0\le v <32768),可以进行如下操作之一改变 vv 的值无限次:

  • v(v+1)mod32768v\Leftarrow (v+1)\bmod 32768
  • v(2×v)mod32768v\Leftarrow (2\times v)\bmod 32768

dvd_v 为整数 vv 被改变至 00 的最小次数,给出一个 nn 个数的数列 a1n(0ai<32768)a_{1\sim n}(0\le a_i <32768),求出所有的 daid_{a_i}

分析

先把所有的 dvd_v 预处理出来,再处理询问。

显然将 vv 改为 00 的最小次数就是将 00 改为 vv 的最小次数,所以操作是可以反着来的,又有 d0=0d_0=0,因此我们把每个数 vv 想象成顶点,根据操作来连边,我们可以通过求反图的最短路来求出所有的 dvd_v,一个 bfs\text{bfs} 就可以实现。

那么应该如何连反边呢,假设我们已经求出了 dvd_v,那么在原图上它可以由以下三个数得到:

  • (v1+32768)mod32768(v-1+32768)\bmod32768
  • (v÷2)mod32768 (v(mod2)=0)(v\div2)\bmod32768\ (v\pmod 2=0)
  • ((v+32768)÷2)mod32768 (v(mod2)=0)((v+32768)\div2)\bmod32768\ (v\pmod 2=0)

因此在 bfs\text{bfs} 中判断这些数是否被更新了,如果没被更新过就更新并加入队列。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=4e4+10;
int n,m=32768,a[N],d[N];
queue<int>q;
int main(){
	for(int i=1;i<m;i++)
		d[i]=-1;
	d[0]=0;
	q.push(0);
	while(q.size()){
		int x=q.front();
		q.pop();
		if(d[(x-1+m)%m]==-1){
			d[(x-1+m)%m]=d[x]+1;
			q.push((x-1+m)%m);
		}
		if(x%2==0&&d[x/2]==-1){
			d[x/2]=d[x]+1;
			q.push(x/2);
		}
		if(x%2==0&&d[(x+m)/2]==-1){
			d[(x+m)/2]=d[x]+1;
			q.push((x+m)/2);
		}
	}
	n=read();
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		cout<<d[a[i]]<<" ";
	return 0;
}
posted @ 2022-04-11 22:07  luckydrawbox  阅读(10)  评论(0)    收藏  举报  来源