【洛谷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;
}
待到再迷茫时回头望,所有脚印会发出光芒