题意:
给你两个数a,b,你有三种操作:
1.a=a+1
2.b=b+1
3.a|=b
问你最少需要几步才能将a=b。
题解:
好吧我承认是稍微想了一会…或许是因为以前这种题目都是交给队友切的,就想的不是很快,并且感觉应该不是最简单的方法,等博客写完之后去康康别人怎么做的。
这个思路的一个很关键的点在于题目里说了
∑
b
<
=
1
e
6
\sum b<=1e6
∑b<=1e6。所以我们可以枚举最终答案为b~a|b。最多到a|b,否则就可以直接3操作加2操作。
假设现在的答案是i,那么我们肯定是b先加到i,然后a再变成b的子集,再进行或。可能会产生这样的疑问:为什么不在b一开始或者加的过程中a进行或之后,b再加到a的值?
那就不能b加到那个值之后a再或吗,脱裤子放屁增加思考量。
或许会产生一些新的疑问,但可以证明是不需要考虑那些问题的,我自己在做的时候也产生了一些思考。
那么现在要解决的就是,a怎么才能变成i的子集、如果你暴力地去枚举子集的话,TLE。或许有一些新奇的方法,但我使用的是最笨的。对于i比a高的位我们先不考虑,我们考虑a拥有的这些位与i进行异或会是什么结果。因为我们要使得a变成i的子集的步数最少。比如:
i=1101010B
a= 1110B
那么经过操作之后s=100B (意义与代码中相等)
接下来需要去找我们要补到i的哪里。既然是补,那么补完的值肯定要比s大,并且在那一位i的值是1,a的值是0.如果a的值是1的话,那你补上来1的时候不就进位了吗。
就上面这个例子,我们需要将a补成100000B才行。
这看起来和s毫无关系,直接a去补就可以了?考虑这种情况:
i=1101010B
a= 100100B
那么s=100B
这个时候我们是不是只需要将a补成101000B就可以了?所以归根结底s只是规范了下限,具体补到多少还是看a和i的情况。
至于怎么找a走多少步,我是用最笨的方法:假设我们现在要补到t,那么将a中位数>=t的1全部消去,剩下的hav就是我们拥有的值,t-hav就是需要补的值。
挖槽真的烦,不会有人像我一样笨笨的方法吧。
补充:好久没写确实是僵硬了,找不一样的位置直接(a|b)-b不就好了…,找最后一位的话直接lowbit(b-((a|b)-a))…之类的吧,还是要恢复恢复,而且看他们题解里好像直接for一遍b就好了?暂时搞的不是很懂。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int tim;
scanf("%d",&tim);
while(tim--){
int a,b;
scanf("%d%d",&a,&b);
int ans=min(b-a,(b|a)-b+1);
for(int i=b;i<=(b|a);i++){
int step=i-b;
int t=1,s=0;
while(t<=a){
if(t&a && (t&i)==0)
s+=t;
t<<=1;
}
if(s==0){
ans=min(ans,step+1);
continue;
}
t=1;
while(t<=s)t<<=1;
while(t<=i){
if(t&i && (t&a)==0)
break;
t<<=1;
}
int hav=a;
for(int k=t;k<=a;k<<=1)
if(k&a)
hav^=k;
ans=min(ans,step+1+t-hav);
}
printf("%d\n",ans);
}
return 0;
}