1. 程式人生 > 實用技巧 >【洛谷4901】排隊(樹狀陣列)

【洛谷4901】排隊(樹狀陣列)

點此看題面

  • 現有一個長為\(n\)的序列\(1,2,...,n\)
  • 定義\(Fib(i)\)為斐波那契數列,其中\(Fib(1)=1,Fib(2)=2\)
  • 對這個序列進行若干次操作,每次將原序列中第\(Fib(1),Fib(2),...,Fib(x)\)個元素取出得到一個新的序列。
  • 問得到的第\(k\)個序列中所有元素乘積能分解得到的質因子個數。
  • 資料組數\(\le10^6,n\le5\times10^6,k\le10^4\)
  • 卡時卡記憶體,限制\(666ms,39.46MB\)

關於質因子個數:線性篩

首先很容易發現若干數乘積的質因子個數等於這些數質因子個數之和。

而一個數的質因子個數可以直接線性篩。

注意,由於此題卡記憶體,而一個數的質因子個數很少(不超過\(logn\)個),因此我發現一種神奇的卡記憶體方法:可以把這個陣列開成\(char\)。。。

模擬:樹狀陣列上二分

首先發現資料組數和\(n\)的範圍都很大,肯定有貓膩。仔細推敲一下就發現,\(n\)的大小並不影響序列的分法,只要求出\(n=5\times10^6\)時對應的序列,每次保留小於等於\(n\)的那部分字首即可。

考慮斐波那契數列增長速度很快(實際上\(Fib(34)\)就已經超過\(5\times10^6\)了),因此我們完全可以直接模擬求出每個序列中的元素。

顯然這可以線段樹上二分,但是為了好寫/卡時/卡空間,可以考慮寫成樹狀陣列上二分。

而詢問的處理非常簡單,由於每個序列長度不超過\(33\),直接列舉第\(k\)個序列,找到其中所有小於等於\(n\)的元素,求出其質因子個數之和即可。

程式碼:\(O(33klogn+33T)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000000
#define K 10000
#define pb push_back
using namespace std;
int Pt,P[350000],Fib[35],a[K+5][35];char f[N+5];bool vis[N+5];
I void Sieve()//線性篩
{
	for(RI i=2,j;i<=N;++i) for(!vis[i]&&(f[P[++Pt]=i]=1),
		j=1;j<=Pt&&i*P[j]<=N;++j) if(vis[i*P[j]]=1,f[i*P[j]]=f[i]+1,!(i%P[j])) break;
}
struct TreeArray
{
	#define V 8388608
	int a[V+5];I void U(RI x) {W(x<=V) ++a[x],x+=x&-x;}I int G(RI k)//樹狀陣列上二分
	{
		RI l=1,r=V,mid,t;W(l^r) mid=l+r>>1,
			(t=mid-l+1-a[mid])<k?(l=mid+1,k-=t):(r=mid);return l;
	}
}T;
int main()
{
	RI i,j,t,s=N;for(Sieve(),Fib[0]=Fib[1]=1,i=2;i<=33;++i) Fib[i]=Fib[i-2]+Fib[i-1];//預處理斐波那契數列
	for(i=1;i<=K;s-=j-1,++i) for(j=1;j<=33;++j) T.U(t=T.G(Fib[j]-j+1)),a[i][j]=t;//模擬預處理出全部序列
	RI Tt,n,k;scanf("%d",&Tt);W(Tt--)//處理詢問
	{
		for(scanf("%d%d",&n,&k),t=0,i=1;i<=33&&a[k][i]<=n;++i) t+=f[a[k][i]];//暴枚找到所有小於等於n的元素,求出質因子個數之和
		i^1?printf("%d\n",t):puts("-1");
	}return 0;
}