您的位置:首页 > 其它

[bzoj4635]数论小测验 解题报告

2016-07-05 11:23 260 查看
感觉自己好蠢只会反演。。跑了整整10s。。

ans=∑k=lr∑i=1⌊mk⌋μ(i)⌊⌊mk⌋i⌋n

这样时间复杂度就是O(∑m√i=1i√+∑m√i=1mi−−√)=O(m34).我还化来化去化了半天式子。。结果a了之后才发现原始的式子直接做就可以了。。

这样其实应该是O(m34logn)的,所以我们还需要预处理一下后面的幂,就可以做到O(T(m34+m−−√logn))了。

设f(k,x)表示[1,m]中与k的gcd是x的数的个数,则

ans=∑k=lr(mn−∑d|kf(k,d)μ(kd)[d≠k])

按m从小到大离线处理询问,这样就可以处理f,花费O(m2logm),每次询问是O(mlogm),所以总时间复杂度是O(m2logm+TlogT+Tmlogm)

然后看了claris老司机的题解,感觉好神啊。。原来第一问可以直接考虑反面dp来搞,第二问可以先取反再容斥。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
#include<cstring>
#include<cmath>
#include<algorithm>
const int T=500+5,N=1e7+5;
const int Mod=1e9+7;
int t;

typedef long long LL;
LL power(LL prod,int x){
LL ans=1;
for(;x;x>>=1){
if(x&1)ans=ans*prod%Mod;
prod=prod*prod%Mod;
}
return ans;
}

namespace N1{
const int M=1e7;
bool p[M+5];
int prime[M];
int mu[M+5];
int smu[M+5];

const int R=5000+5;
LL pwr1[R],pwr2[R];
void work(){
mu[1]=1;
for(int i=2;i<=M;++i){
if(!p[i]){
prime[++prime[0]]=i;
mu[i]=-1;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=M;++j){
p[i*prime[j]]=1;
if(i%prime[j])mu[i*prime[j]]=-mu[i];
else break;
}
}
for(int i=1;i<=M;++i)smu[i]=smu[i-1]+mu[i];

while(t--){
int n,m,l,r;
scanf("%d%d%d%d",&n,&m,&l,&r);

int root=sqrt(m);
for(int i=root;i;--i){
pwr1[i]=power(i,n);
pwr2[i]=power(m/i,n);
}

LL ans=0;
for(int i=1,j,tmp=m/i;i<=m&&tmp>=l;i=j+1,tmp=m/i){
j=m/tmp;

//printf("---[%d,%d]---\n",i,j);

LL s=0;
for(int k=l,o;k<=min(tmp,r);k=o+1){
o=min(tmp/(tmp/k),r);
if(tmp/k<=root)s=(s+(o-k+1)*pwr1[tmp/k])%Mod;
else s=(s+(o-k+1)*pwr2[i*k])%Mod;

//printf("[%d,%d]=%I64d\n",k,o,(o-k+1)*power(tmp/k,n)%Mod);
}
ans=(ans+s*(smu[j]-smu[i-1]))%Mod;
}
printf("%lld\n",(ans+Mod)%Mod);
}
}
}
#include<vector>
namespace N2{
const int M=1000;
int cnt[M+5][M+5];

bool p[M+5];
int prime[M];
int mu[M+5];

int gcd[M+5][M+5];

struct QS{
int n,m,l,r,i;
bool operator < (const QS & o)const{
return m<o.m;
}
}que[T];
LL ans[T];
void work(){
mu[1]=1;
for(int i=2;i<=M;++i){
if(!p[i]){
prime[++prime[0]]=i;
mu[i]=-1;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=M;++j){
p[i*prime[j]]=1;
if(i%prime[j])mu[i*prime[j]]=-mu[i];
else break;
}
}

for(int i=M;i;--i)gcd[i][0]=gcd[0][i]=i;
for(int i=1;i<=M;++i)
for(int j=1;j<=i;++j)
gcd[i][j]=gcd[j][i]=gcd[j][i%j];

for(int i=1;i<=t;++i){
scanf("%d%d%d%d",&que[i].n,&que[i].m,&que[i].l,&que[i].r);
que[i].i=i;
}
sort(que+1,que+t+1);
for(int i=1,j=0;i<=t;++i){
for(;j<=que[i].m;++j)
for(int o=M>>1;o;--o)
for(int k=o<<1;k<=M;k+=o)
if(mu[k/o]&&o%gcd[k][j]==0)
++cnt[k][o];

ans[que[i].i]=(que[i].r-que[i].l+1)*power(que[i].m,que[i].n)%Mod;
for(int o=1;o<=que[i].r>>1;++o)
for(int k=o<<1;k<=que[i].r;k+=o)
if(k>=que[i].l&&mu[k/o])
ans[que[i].i]=(ans[que[i].i]+mu[k/o]*power(cnt[k][o],que[i].n))%Mod;
}
for(int i=1;i<=t;++i)printf("%lld\n",(ans[i]+Mod)%Mod);
}
}
int main(){
freopen("bzoj_4635.in","r",stdin);
//freopen("bzoj_4635.out","w",stdout);
int type;
scanf("%d%d",&t,&type);
if(type==1)N1::work();
else N2::work();
}


总结:

①两个根号枚举套一起是O(n34)的!

②直接dp确定值考虑反面是很方便的。如果把这种dp展开会发现是个容斥,但是如果直接从容斥的角度考虑就会变得非常麻烦了(因为会有什么至少、至多这种奇怪的东西)。

③虽然反演是一种容斥,但是有的时候不一定需要它,强行上反演反而会变得麻烦的。考虑一下应该具体怎么容斥。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: