Luogu P4901 排队 fib数列+树状数组+倍增

这题让我升华。。还好只重构了一遍


首先我们发现:$n$较小时,整个队伍的形态 跟 $n$ 比较大时的局部是一样的

所以我们预处理出这个队伍的形态,和每一行每个位置的质因子个数的前缀和,$O(nlogn)$,然后每次回答$log(n)$

方法:

1.线性筛,筛出每个数值因子的个数;

2.然后用一个树状数组,维护整个队列中的还存在的数的数量;相当于统计一个数组(设其为$a$)的前缀和,这个数组以自己为下标,存储这个数还存不存在,即$a[x]=1表示x存在,a[x]=0表示x不存在$;刚开始$a$中都为$1$,当某个数$x$被删除时,单点修改$a[x]=0$.

3.查询排名:这里要用到倍增的思路。因为当树状数组的下标$pos==2^k$时,它维护的是$[1,2^k]$的前缀和,所以根据树状数组的思想,可以倍增查找;

4.因为每次查询后都会删一个数,所以查询的实际位置是$fib[i]-已经删去的数的个数,即fib[i]-i+1$

5.最后对于每一个询问,在预处理出的 队伍形态 的数组的第$k$行中,查找编号小于但最接近$n$的那个数数的位置,对应到前缀和数组中的位置就是答案。

但是自己还有个疑问:为何空间开一半就够了。。。自己一直MLE,看了题解才开了一半qwq

PS:bitset比bool 快一些

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<bitset>
#define R register int
const int N=5000000,K=10000;
using namespace std;
inline int g() {
    R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
    do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
}
int c[N+1],fib[38];
inline int lbt(int x) {return x&-x;}
inline void add(int pos,int vl) {for(;pos<=N;pos+=lbt(pos)) c[pos]+=vl;}
int pri[N/11],tot;
short cnt[N+1];
bitset<N+1> vis;
inline void PRI() {
    for(R i=2;i<=N;++i) {
        if(!vis[i]) pri[++tot]=i,cnt[i]=1;
        for(R j=1;j<=tot&&i*pri[j]<=N;++j) {
            vis[i*pri[j]]=true,cnt[i*pri[j]]=cnt[i]+1;
            if(i%pri[j]==0) break;
        }
    } 
}
vector<int> sum[K+1],num[K+1];
inline int query(int rk) {
    R pos=0,tot=0; for(R t=22;t>=0;--t) 
        if(tot+c[pos+(1<<t)]<rk) pos+=(1<<t),tot+=c[pos];
    return pos+1;
}
inline void PRE() { PRI(); fib[1]=1,fib[2]=2;
    for(R i=3;i<=37;++i) fib[i]=fib[i-1]+fib[i-2];
    for(R i=1;i<=N;++i) add(i,1); R tot=N;
    for(R i=1;i<=K;++i) for(R j=1;fib[j]-j+1<=tot;++j) {
        R pos=query(fib[j]-j+1); add(pos,-1),--tot; //cout<<pos<<" "<<cnt[pos]; putchar('\n');
        if(j>=2) sum[i].push_back(sum[i][j-2]+cnt[pos]);
        else sum[i].push_back(cnt[pos]);
        num[i].push_back(pos);
    }
}
signed main() { PRE(); R t=g();
    for(R i=1;i<=t;++i) {
        R n=g(),k=g(); if(num[k][0]>n) {printf("-1\n"); continue;}
        R pos=upper_bound(num[k].begin(),num[k].end(),n)-num[k].begin()-1;
        printf("%d\n",sum[k][pos]);
    } //while(1);
}

2019.05.17

猜你喜欢

转载自www.cnblogs.com/Jackpei/p/10879066.html