【好题选讲】P10026 /【LGR-169-Div.2】T3 哀之变化

前言

想要完成这道题目,你需要:

  • 小学的四则运算知识
  • 奇偶数的性质熟练掌握
  • 正难则反的思想
  • 惊人的注意力

难度评级:\(\color{green}{\text{普及+/提高}}\)

1. 35pts 思路

拿到题目:搜索?启动!
于是我们很简单的拿到了 \(35\) 分。
不过我们的目标是AC,先保留着这个暴力,对以后我们猜结论可能有很大的帮助。

2. 100pts 思路

观察到题目的数据范围是 \(1\le\cdots\le10^{18},1\le T \le 10^5\),这提示我们对每一组测试数据,要使用最多 \(O(\log{n})\) 的时间求解。
根据我们惊人的做题经验,我们想到了反着推——即把操作变成 \(\div2\)\(+1\),从 \(n\) 推到 \(1\)
有了这样的思路后,我们发现可以求从 \(n\rightarrow 1\) 所需要的最小操作数了!
怎么求呢?我们发现,不论何时,\(\div2\) 操作总是比 \(+1\) 操作的贡献要大:即可以让 \(n\) 缩减的更快。但是,当被除数为奇数时则不能继续 \(\div 2\) 了。因为我们反向推的实质是将正着推反了过来,因为在正向推的过程中,不可能出现浮点数,所以在反向推的过程中也不应出现,因此要有 \(+1\) 操作将其变为偶数后在进行 \(\div 2\) 操作。
找最小步数的操作可以用 while 循环实现,时间复杂度 \(O(\log n)\),符合我们的要求,代码如下。

int minst=0;//最小步数,不要初始化为1!!! 
while(n!=1){
    if(n%2!=0){
	    n++;
	}
	else{
		n/=2;
	}
	minst++;
}

但是,题目中给的要求是恰好 \(k\)\(1\rightarrow n\),即恰好 \(k\)\(n\rightarrow 1\),怎么办?
也很好想:我们既然已经求解了最小步数,那么把剩下的步数浪费了即可。
问题又来了,怎么浪费?
我们先设多余的(即要浪费的步数)为 \(t\),则 \(t=k-\text{最小步数}\)
则有一个很显然的结论:\(t<0\) 时,这组数据一定无解。
对于其它的数,我们的思路是在变换的过程中找到一些循环,这样我们在变换时就可以不停走这些循环来浪费一定的步数。
这时候,惊人的注意力让我们发现了这样一个循环:

\[1=(1+1)\div2=((1+1)\div2+1)\div 2 \]

变成题目里的正向也就是:

\[1=1\times 2-1=(1\times 2-1)\times 2-1 \]

也就是说,当我们用最少步骤到了 \(1\) 时,我们可以通过不停的 \(+1\div 2\) 来每次浪费两步。
于是,我们又可以得到结论:\(t\)\(2\) 的倍数时,这组数据有解。

我在比赛时只想到了这里,太菜了qwq.

那么,当 \(t\) 为奇数是什么情况呢?
惊人的注意力又让我们发现了这样一个循环:

\[2=(2+1+1)\div2=((2+1+1)\div2+1+1)\div2 \]

变成题目里的正向也就是:

\[2=2\times 2 -1-1=(2\times 2 -1-1)\times 2 -1-1 \]

这样,我们就可以每次浪费三步。
这时候,惊人的注意力又让我们发现了:
因为 \(t\) 为奇数,则 \(t-3\) 一定是偶数,这样我们就可以套用上面的每次浪费两步的循环成功求解这一组数据。且 \(t-3\ge0\)
所以我们又有一个结论:
\(t\ge 3\) 时,这组数据有解。
那么,现在还剩一个最特殊的 \(t=1\) 的情况了。我们通过搜索发现,当 \(t=1\) 时,数据有时有解有时无解。
而在这时我们就不应去思考循环的影响了:因为仿照上面的两个循环,我们可以找到 \(4\times 2-1-1-1-1\)\(8\times 2-1-\cdots-1\) 等诸如此类的循环,但是它们都不能讨论到 \(t=1\) 的情况。
那唯一的可能,就是在求解的过程中,有一个或一组操作可以使需要的步数增加 \(1\),让这组数据有解,而且这样的一个或一组操作还不一定出现。
这样的操作怎么找呢,我们不妨列举一下求解过程中可能出现的情况:

\[n\cdots\div2\div2\cdots 1 \]

\[(n\cdots+1)\div2\cdots 1 \]

\[n\cdots\div2+1\cdots 1 \]

特殊的,没有 \((n+1+1+1)\div2\cdots 1\) 的情况(最小步数!)
把上面两种操作变成正向的:

\[1\cdots\times 2\times 2\cdots n \]

\[1\cdots\times 2-1\cdots n \]

\[(1\cdots-1)\times 2\cdots n \]

惊人的注意力又让我们发现了:
对于操作 \((1\cdots-1)\times 2\cdots n\) 可以改写为 \(1\cdots \times 2-1-1\)。这样,我们把这个过程由两步变成了三步,成功解决了 \(t=1\) 的情况。
我们也发现,这样的操作不一定在所有最短变换序列中存在,这也验证的我们在暴力程序的发现。
所以我们得出最后一个结论:\(t=1\) 且求最短步数过程中出现如上步骤,这组数据有解。
而当以上所有都不满足时,这组数据无解。

3. 代码实现

其实很容易实现!
注意的是 \((1\cdots-1)\times 2\cdots n\) 在求解过程中的判断:由于是反着求解,因此判断也要反着来。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
ll n,k;
int main(){

	ll T;
	T=read();
	while(T--){
		k=read();
		n=read();
		if(n==0){
			if(k>=1){
				cout<<"Yes"<<'\n';
				continue;	
			}
			else{
				cout<<"No"<<'\n';
				continue;
			}
		}
		else if(n==1){
			if(k!=1&&k!=3){
				cout<<"Yes"<<'\n';
				continue;	
			}
			else{
				cout<<"No"<<'\n';
				continue;
			}	
		}
		ll t=0;
		bool isadd=0,isdiv=0;
		while(n!=1){
			if(n%2!=0){
				n++;
				if(isadd==1)
					isdiv=1;
				isadd=0;
			}
			else{
				n/=2;
				isadd=1;
			}
			t++;
		}
		t=k-t;
		if(t<0)cout<<"No"<<'\n';
		else if(t%2==0)cout<<"Yes"<<'\n';
		else if(isdiv&&t==1)cout<<"Yes"<<'\n';
		else if(t>=3)cout<<"Yes"<<'\n';
		else cout<<"No"<<'\n';		
	}
	return 0;
}

迁移自洛谷

posted @ 2025-02-04 12:08  hm2ns  阅读(6)  评论(0)    收藏  举报