Codeforces Round 941 (Div. 2) D

HL_ZZP發表於2024-04-28

好坐牢的一次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\)的具體數字,我應該怎麼做來湊出他。但是現在來看,這個想法是不行的。這個思路在之前的我的腦子裡面是不存在的,我需要加上,對於不同的大小的劃分,有些部分不需要嚴絲合縫,只需要在某一個層面上嚴謹即可。
哎,被一個思路限死了,我當時確實是沒有辦法。

相關文章