#C231116B. 排列(perm)
前言(Front talk)
网址(Website)
题目(Problem)
给定一个长度为
每次询问给定一个区间
其中
其中
- 当
时,其值为 ; - 当
时,其值为 ; - 若无限循环,将其值看作
。
对于
题解(Solution)
当时是这么分析的:
然后就可以推理出:
看起来非常的复杂,其实除了
但是,把上面的公式再进行总结,就可以得出:
这个点应该是在开始讲的,但是为了还原我的思维过程(当时我注意到了这个点,但是没在意),我放到最后来讲,这里的
int d=gcd(x,y);
if(x/d+y/d==2^k)
f(x,y)=(int)log2((x+y)/d)
这样就可以只从质数的角度来思考问题
然后思考如何将通过此题:
枚举 的所有 的次幂;for(int k=2;k<=20;k++)
枚举奇数,满足 ,为什么偶数不行呢?因为要互质。 枚举 ,并将值 与值 所对应的位置连边,,注意从右到左连,方便后续处理。因为题目条件是==排列==,因此才可以在这样的时间内枚举完。这也是我当时忽略的地方。
接下来就是处理每一条连边,或者说每一个区间,对于每一个询问
代码(Code)
#include<bits/stdc++.h>
#define For(i,l,r) for(int i=l;i<=r;++i)
#define Rof(i,l,r) for(int i=l;i>=r;--i)
using namespace std;
inline int input(){
char c;bool f=false;while(!isdigit(c=getchar()))f=c=='-';int x=c^48;
while(isdigit(c=getchar())){x=(((x<<2)+x)<<1)+(c^48);}return f?-x:x;
}const int N = 1012345;
vector<pair<int,int>> ft[N],que[N];
int n,q,c[N],a[N],id[N];
int ans[N];
//树状数组
inline int lbt(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lbt(i))c[i]+=k;}
inline int ask(int x){int res=0;for(int i=x;i;i-=lbt(i))res+=c[i];return res;}
signed main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
n=input();For(i,1,n) id[a[i]=input()]=i;
q=input();For(i,1,q){
int l=input(),r=input();
que[r].emplace_back(l,i);
}for(int k=2;(1<<k)<=n*2;++k){//每次的贡献 时间复杂度 O(logn)
int sum=(1<<k);//相当于 2^k
for(int j=1;j*2<=sum;j+=2){//枚举所有的奇数
for(int x=j,y=sum-j;y<=n;x+=j,y+=sum-j){//因为是排列,所以可以枚举因数d(其实就是倍数),然后直接连边
int l=min(id[x],id[y]),r=max(id[x],id[y]);
ft[r].emplace_back(l,k);
}
}
}For(r,1,n){
for(auto p:ft[r]){
int l=p.first,v=p.second;
update(l,v);
}for(auto p:que[r]){
int l=p.first;
ans[p.second]=ask(r)-ask(l-1);
}
}For(i,1,q) printf("%lld\n",ans[i]);
return 0;
}
后续(Ending)
讲一些新的思路或方法:
- 通过树状数组维护一段区间的个数,或者说通过枚举
,去掉了区间的一部分影响,简化问题; - 通过对答案的离线简化问题,然后转成在线按位置处理;
- 从值域的角度出发,将位置的影响去掉(具体可见代码中的
数组)。