Codeforces Round 941 (Div. 2) D
好坐牢的一次div2
ABC是速通的,结果cf的pretest太弱了。。然后我这次因为想快点,没有再去好好顺一遍思路,状态又不太好,写了一个好简单的错,结果过了。导致我被hack了,爆掉100分。
好烦。
主要说说这个D。
现在能够从算式的层面上理解了这个做法的正确性,就是把二进制位的数字放进去,然后把k的最高位的1对应的二进制数字去掉,加上下面三个数字\(k-2^i,k+1,k+2^i+1\),其中\(i\)是k的最高位1的位置。
然后推导一下,分为3个部分
第一个部分,对于\(1\leq v <2^i\)的部分,直接二进制分解正常做
第二个部分,对于\(2^i\leq v<k\)的部分,我们先把所有\(j<i\)的\(2^j\)全部选上。这个时候,这个整体的值\(=2^i-1\)。我们的v是大于\(2^i\)的,也就是说,我们是一定需要\(k-2^i\)这个数字的,否则仅是用那些下面的数字是做不到的。我们先把下面的数字全部选上,得到的和是\(2^i-1\),再加上这个数字,得到的和是\(k-1\)。我们的目标是v,也就是我们需要从\(k-1\)中选择一些删去。因为\(v\leq k-1\),所以,一定能够通过减去一些数字得到v,而v的最高位是和k一样的,所以最高位一定不会减去。我们已经把所有\(<i\)的位置全部选择,也就是我们只用通过选择那些已经选上了的位置哪些不要就可以凑出所有\(\leq 2^i-1\)的数字,这也是v和k不一样的地方。正确性得证了
第三个部分,对于\(k<v\),\(v-k-1\)明显是大于0的,也就是可被凑出来的,那用这个数字加上\(k+1\)就可以了。
假如,凑出\(v-k-1\)需要用到\(2^i\)这个数字,那就把\(k+1\)换成\(k+1+2^i\)就可以了。
这三个部分都是可实现的,正确性有了。但是tnnd怎么想啊,这怎么想的到的啊。
题解只讲做法,不讲思路。唉,仅仅是"题解"的题解,总感觉是不完整的。
首先二进制分解的思路是没有问题的。有问题的是构造的方法,准确说是怎么想到这个构造的方法,有没有一个顺畅自然的思路。
这题还是挺少见的。但是普通的思路却又没有办法,就很恶心。很明显的感觉到我缺了一部分,但是又不知道是那一部分。考察在了一个隐蔽的点吧。
从开始考虑吧。
首先,我要能够凑出1到n的所有数字,二进制分解是绝对的思路。
那现在的问题是,如何改写其中的几个数字来满足我们无法得到k的需要。
大框架是不能动的,否则是很麻烦的,而且很明显二进制已经是唯一的解法了。
对于简化的问题,假如k的二进制表示一定只含有1个1,那解法是很简单的。我们的数组里面不包含这个数字,那我们对于比这个数字大和比这个数字小的数字分别讨论,这个是很简单的,对于小的不用考虑,对于大的,就只需要补一个\(k+1\)和\(k+2^i\)就好了。
这个已经出来了,其实真正卡住我的就是上面题解的第二个部分的构造方法。感觉比赛的时候没有专门把第一类和第二类区别开,主要是当时也没有确定思路就是去掉最高位。当时也是尝试去掉最高位,然后就发现了第二类的构造困难,也就是,我没有\(2^i\)这个高位,我需要补上一个什么样的数字来构造其他位置的数字,并且完美避开k呢?到这里就找不到了,想了一个类似锁与钥匙的模型,就是打开\(2^i\)这个数字的锁需要\(k-2^i\)这个数字里面对于的二进制的1没有全部被用上,这个是他的钥匙,但是什么样的构造能满足这样的需求?想不到。
看看上面的答案,非常喵,构造的答案都写出来了。
真是。。戏剧性啊。。🤡
其实思路就是用差的思路。\(k-2^i\),其中我把所有比这个小的数字用上,那也是刚刚好变成\(k-1\),那么,根据二进制分解,我能够用这个办法,算出,从\(k-2^i\)到\(k-1\)的,所有数字,然后,再看看,我们要求的范围?\(2^i\leq v<k\),我们现在能求什么?\(k-2^i\)。\(i\)是最高位。\(k< 2^{i+1}\) \(k-2^i< 2^{i}\)。完美覆盖,甚至重合。
然后是高位的,直接给个\(k+1\)其实就好了,那要凑得数字就变成了\(n-k-1\)了,这个数字自然是可得到的,除非它等于k。所以可能需要再加一个数字,就\(k+1+2^i\) 吧。很明显,这个是可以的。
唉,真的好烦,这题目,构造真的很巧妙,好难想,我总结出来的思路就只有一个反向的求商。。。但是我在遇到这个题目,真的能做出来吗。。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
int main()
{
// cout<<(1LL<<25)<<endl;
ll T=read();
while(T--)
{
ll n=read(),k=read();
vector<ll> ans;
ll fir,las;
for(ll i=40;i>=0;i--)
{
if((k>>i)&1)
{
fir=i;
break;
}
}
ans.push_back(k+1);
ans.push_back(k+1+(1LL<<fir));
ans.push_back(k-(1LL<<fir));
for(int i=0;i<=19;i++)
{
if(i!=fir)
{
ans.push_back((1LL<<i));
}
}
cout<<ans.size()<<endl;
for(ll i=0;i<ans.size();i++)
{
cout<<ans[i]<<' ';
}
cout<<endl;
}
return 0;
}
还是那个问题,我下次遇到,真的能做出来吗。其实就是那个转化为商的做法。我当时没有去想对于第二类这个具体的范围,而是在想对于一个含有\(2^i\)的具体数字,我应该怎么做来凑出他。但是现在来看,这个想法是不行的。这个思路在之前的我的脑子里面是不存在的,我需要加上,对于不同的大小的划分,有些部分不需要严丝合缝,只需要在某一个层面上严谨即可。
哎,被一个思路限死了,我当时确实是没有办法。