[FJOI2015] 世界樹 題解

Supor__Shoop發表於2024-09-27

咱們看到了資料範圍之後,不要畏懼,因為這說明正解是一個結論,推出結論再套一個高精咱們就勝利啦!!!

首先可以判斷需要貪心,就是說我們要儘可能保證多的點,滿足其左右子樹的最大深度相差 \(1\)

從大局觀入手,我們現在得到樹根 \(r\),其左右兒子分別為 \(l,r\),令 \(l\) 子樹中的最大深度 \(\leq\) \(r\) 子樹中的最大深度,且此時的樹是一個最優解。不難想到這是一個分而治之的過程:即滿足了 \(|dis_l-dis_r|\leq 1\) 的條件過後,再在左右子樹進行分治,重複以上過程。

由此就可以想到,設 \(dp_i\) 表示深度為 \(i\) 時滿足題目條件的樹的最小點數。考慮轉移,首先要滿足深度為 \(i\) 那麼必定有一個子樹為 \(dp_{i-1}\),而還要保證點數最小且 \(|dis_l-dis_r|\leq1\),那麼另外一個子樹就是 \(dp_{i-2}\),再加上根節點,轉移就是:

\[dp_i=dp_{i-1}+dp_{i-2}+1 \]

初始化:\(dp_1=1,dp_2=2\)

那麼我們就可以得到 \(n=dp_i\) 時的答案了,就是 \(\lfloor \dfrac{i+1}{2}\rfloor-1\)

接著我們就可以嘗試擴充到其它 \(n\) 的答案。對於 \(n\in [dp_i,dp_{i+1})\),我們肯定是考慮保持 \(\lfloor \dfrac{i+1}{2}\rfloor-1\) 這個答案,即先構造出 \(n=dp_i\) 的最優的樹,然後在上面新增 \(n-dp_i\) 個節點,儘可能不改變葉子節點的高度極差。不難發現僅有 \(n=6\) 的時候無法保持 \(n=dp_3=4\) 時候的答案,而其餘的都滿足最終答案為 \(n=dp_i\) 時的答案。

因此我們求出 \(dp_i\) 陣列,然後找到 \(i\) 滿足 \(n\in[dp_i,dp_{i+1})\),如果 \(n≠6\),則輸出 \(\lfloor \dfrac{i+1}{2}\rfloor-1\),否則特判答案為 \(0\)

由於 \(n\) 太大力,因此我們需要一個高精度的陣列。透過打表找到最大的 \(i\) 接近於 \(5\times 10^4\),然後預處理 \(dp_i\)。然而按照上述寫法你會發現 MLE 啦。。

「????」

我們發現這樣做,預處理需要的空間太大了,然而最終查詢用到的只有 \(50\) 個位置,因此考慮最佳化一下,將所有詢問離線下來排序,然後使用滾動陣列,一邊遞推一邊統計答案就行了。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=55;
namespace BigInterger{//找同學要的壓位高精度板子 
	struct Int{
		int sign;
		std::vector<int>v;
		inline Int():sign(1){}
		inline Int(const std::string&s){*this=s;}
		inline Int(const int&v){
			char buf[21];
			sprintf(buf,"%d",v);
			*this=buf;
		}
		inline void zip(const int&unzip){
			if(!unzip)
				for(int i=0;i<v.size();++i)
					v[i]=get_pos(i<<2)+ get_pos(i<<2|1)*10+get_pos(i<<2|2)*100+get_pos(i<<2|3)*1000;
			else
				for(int i=(v.resize(v.size()<<2),v.size()-1),a;~i;--i)
					a=((i&3)>=2)?v[i>>2]/100:v[i>>2]%100,v[i]=(i&1)?a/10:a%10;
			setsign(1,1);
		}
		inline int get_pos(const int&pos)const{return pos>=v.size()?0:v[pos];}
		inline Int&setsign(const int&newsign,const int&rev){
			while(v.size()>1&&!v.back())v.pop_back();
			sign=(v.empty()||(v.size()==1&&!v[0]))?1:(rev?newsign*sign:newsign);
			return *this;
		}
		inline std::string to_str()const{
			Int b=*this;
			std::string s;
			for(int i=(b.zip(1),0);i<b.v.size();++i)
				s+=(char)(*(b.v.rbegin()+i)^48);
			return (sign<0?"-":"")+(s.empty()?"0":s);
		}
		inline bool absless(const Int&b)const{
			if(v.size()!=b.v.size())return v.size()<b.v.size();
			for(int i=v.size()-1;~i;--i)
				if(v[i]!=b.v[i])return v[i]<b.v[i];
			return false;
		}
		inline Int operator-()const{
			Int c=*this;
			c.sign=(v.size()>1||v[0])?-c.sign:1;
			return c;
		}
		inline Int&operator=(const std::string&s){
			if(s[0]=='-')
				*this=s.substr(1);
			else{
				for(int i=(v.clear(),0);i<s.size();++i)
					v.push_back(*(s.rbegin()+i)^48);
				zip(0);
			}
			return setsign(s[0]=='-'?-1:1,sign=1);
		}
		inline bool operator<(const Int&b)const{
			return sign!=b.sign?sign<b.sign:(sign==1?absless(b):b.absless(*this));
		}
		inline bool operator==(const Int&b)const{return v==b.v&&sign==b.sign;}
		inline Int&operator+=(const Int&b){
			if(sign!=b.sign)return *this=(*this)-(-b);
			v.resize(std::max(v.size(),b.v.size())+1);
			for(int i=0,carry=0;i<b.v.size()||carry;++i){
				carry+=v[i]+b.get_pos(i);
				v[i]=carry%10000,carry/=10000;
			}
			return setsign(sign,0);
		}
		inline Int operator+(const Int&b)const{
			Int c=*this;
			return c+=b;
		}
		inline Int operator++(){return *this+=1;}
		inline Int operator-(const Int&b)const{
			if(b.v.empty()||b.v.size()==1&&!b.v[0])return *this;
			if(sign!=b.sign)return (*this)+(-b);
			if(absless(b))return -(b-*this);
			Int c;
			for(int i=0,borrow=0;i<v.size();++i){
				borrow+=v[i]-b.get_pos(i);
				c.v.push_back(borrow);
				c.v.back()-=10000*(borrow>>=31);
			}
			return c.setsign(sign,0);
		}
		inline Int operator-=(const Int&b){return *this=*this-b;}
		inline Int operator--(){return *this-=1;}
		inline void add_mul(const Int&b,int mul){
			v.resize(std::max(v.size(),b.v.size())+2);
			for(int i=0,carry=0;i<b.v.size()||carry;++i){
				carry+=v[i]+b.get_pos(i)*mul;
				v[i]=carry%10000,carry/=10000;
			}
		}
		inline Int operator*(const Int&b)const{
			if(b<*this)return b*(*this);
			Int c,d=b;
			for(int i=0;i<v.size();++i,d.v.insert(d.v.begin(),0))
				c.add_mul(d,v[i]);
			return c.setsign(sign*b.sign,0);
		}
		inline Int operator*=(const Int&b){return *this=*this*b;}
		inline Int operator/(const Int&b)const{
			if(b.v.size()==1&&!b.v[0]){
				std::cout<<"Divisor is 0.\nCheck your code now.\n";
				exit(0);
				return 0x7fffffff;
			}
			Int c,d;
			d.v.resize(v.size());
			double db=1.0/(b.v.back()+(b.get_pos((unsigned)b.v.size()-2)/1e4)+(b.get_pos((unsigned)b.v.size()-3)+1)/1e8);
			for(int i=v.size()-1;~i;--i){
				c.v.insert(c.v.begin(),v[i]);
				int m=(c.get_pos(b.v.size())*10000+c.get_pos((int)b.v.size()-1))*db;
				c-=b*m,c.setsign(c.sign,0),d.v[i]+=m;
				while(c>=b)c-=b,++d.v[i];
			}
			return d.setsign(sign*b.sign,0);
		}
		inline Int operator/=(const Int&b){return *this=*this/b;}
		inline Int operator%(const Int&b)const{return *this-*this/b*b;}
		inline Int operator%=(const Int&b){return *this=*this%b;}
		inline bool operator>(const Int&b)const{return b<*this;}
		inline bool operator<=(const Int&b)const{return !(b<*this);}
		inline bool operator>=(const Int&b)const{return !(*this<b);}
		inline bool operator!=(const Int&b)const{return !(*this==b);}
	};
	inline std::istream& operator>>(std::istream& stream,Int&x){
		std::string str;
		stream>>str;
		x=str;
		return stream;
	}
	inline std::ostream& operator<<(std::ostream& stream,const Int&x){
		stream<<x.to_str();
		return stream;
	}
}using BigInterger::Int;
struct node
{
	Int x;
	int id;
	bool operator<(const node &f)const{ return x<f.x; }
}q[MAXN];
int res[MAXN];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	for(int i=1;i<=T;i++)	cin>>q[i].x,q[i].id=i;
	sort(q+1,q+T+1);//大力離線 
	Int A=1,B=2,C;//滾動需要的變數 
	int j=1;
	for(int i=1;i<=T;i++)
	{ 
		while(B<=q[i].x)	C=A,A=B,B=C+B+1,j++;
		if(q[i].x<=2||q[i].x==6)	continue;//特判 
		res[q[i].id]=ceil(j/2.0)-1;//統計 
	}
	for(int i=1;i<=T;i++)	cout<<res[i]<<'\n';
	return 0;
}

相關文章