如果我们将每个数a分解质因式:
所以我们可以统一处理所有共线的向量,这部分向量会有一个基向量a,即每一个向量都可以写成它的若干倍,即
所以就相当于是有一个乘法矩阵,第i行第j列是i*j,求
V1
既然数据范围这么小,根据上面的分析,就可以直接把矩形中所有元素拎出来,排序/set去重
或者也可以不分析,直接按照题意取log/分解质因子/hash判重。
V2
注意到随着a的增大,
所以我们可以从小到大处理每个a,就相当于是需要支持插入、删除一个范围在
或者我们可以这样,每当
当然我们也可以容斥(为什么我一看见不重复就想容斥。。。),假如说在
V3
注意到实际上时间复杂度远远不会及此,因为我们可以对它进行一些行之有效的剪枝。
当底数大于等于
一个很显然的剪枝是如果
另一个关键的剪枝是如果在还没有dfs到的部分存在一个数是当前的lcm的因子,那么会意味着它不会对lcm造成影响,选它和不选它会对应着互为相反数的答案,所以此时答案必然为0,便可以剪枝。
注意到答案的计算只与最小的数、最大的数和lcm有关,所以我们可以先枚举最小的数和最大的数,然后记下搜索的结果,这样在再次枚举的时候就不需要重复搜索了。
如果当前的
如果当前的
在xjb剪了很久之后,终于a掉了!
ac后看到别人的代码,惊讶的发现大家似乎都写的爆搜?那他们怎么跑的那么快!
然后就又学习了一些别人代码的姿势:
算lcm的时候因为是一个很大的数与一个很小的数求gcd,所以其实是可以预处理gcd以做到
容斥可以不记符号,而是把减法写在中间的计算里。
中间没有改变的状态是没有必要dfs下去的,这样只会使搜索树更大,让一个合法的状态消耗很多剪枝的判断。
代码:
#include<stdio.h>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<bitset>
typedef long long LL;
const int N=8e7+5;
bool vst[N];
int l1,r1;
LL l2,r2;
const int Mod=1e9+7;
const int Log=50+5;
int gcd[Log][Log];
int dfs(int x,LL lcm,LL l,LL r,int first,int end)
{
if(l>r)return 0;
for(int i=x;i<end;++i)
if(lcm%i==0)
return 0;
int ans=(r-l+1)%Mod;
LL nextlcm;
for(int i=x;i<end;++i)
{
nextlcm=i/gcd[i][lcm%i]*lcm;
(ans-=dfs(i+1,nextlcm,(l2+nextlcm/end-1)/(nextlcm/end),r2/(nextlcm/first),first,end))%=Mod;
}
return ans;
}
int f[Log][Log];
inline int query(int l,int r)
{
if(!~f[l][r])
if(l==r)f[l][r]=(r2-l2+1)%Mod;
else
{
printf("f[%d,%d]=",l,r);
int lcm=l*r/gcd[r][l];
f[l][r]=(Mod-dfs(l+1,lcm,(l2+lcm/r-1)/(lcm/r),r2/(lcm/l),l,r))%Mod;
printf("=%d\n",f[l][r]);
}
return f[l][r];
}
int main()
{
freopen("51nod1026.in","r",stdin);
LL l,r;
cin>>r2>>r>>l>>l2;
r+=l-1,r2+=l2-1;
//printf("2:[%lld,%lld]\n",l2,r2);
int sqroot=sqrt(r);
LL cnt1=r-max((LL)sqroot,l-1);
int now=0,ans=0;
memset(f,-1,sizeof(f));
for(int i=Log;i--;)gcd[i][0]=i;
for(int i=1;i<Log;++i)
for(int j=i;j;--j)
gcd[i][j]=gcd[j][i%j];
for(int i=2,prel,prer;i<=sqroot;++i)
if(!vst[i])
{
prel=l1,prer=r1;
l1=0;
{
LL j=i;
int k=1;
for(;j<=r/i;j*=i,++k)
{
if(j<=sqroot)vst[j]=1;
else cnt1-=j>=l;
if(j>=l&&!l1)l1=k;
}
if(j>=l)
{
--cnt1;
if(!l1)l1=k;
}
r1=k;
}
if(l1)
{
if(l1!=prel||r1!=prer)
{
//printf("----%d:[%d,%d]----\n",i,l1,r1);
now=0;
for(int k=l1;k<=r1;++k)
for(int j=k;j<=r1;++j)
(now+=query(k,j))%=Mod;
//printf("%lld\n",now);
//printf("%.5f\n",(double)clock()/CLOCKS_PER_SEC);
if(sqroot>=l&&l1==1&&r1==2)
{
(ans+=now)%=Mod;
for(;i<=sqroot;++i)
if(!vst[i])
--cnt1,(ans+=now)%=Mod;
break;
}
}
}
else now=0;
(ans+=now)%=Mod;
}
//printf("%.5f\n",(double)clock()/CLOCKS_PER_SEC);
cout<<((ans+cnt1%Mod*((r2-l2+1)%Mod))%Mod+Mod)%Mod<<endl;
}
总结:
①判重:可以排序
②可以通过取log将指数上的数放下来,以减小数的大小。
③容斥时可以不记符号,把减法写在中间的计算里!
④没有改变的状态是没有必要dfs下去的,这样虽然会更好写,但它也会让搜索树多一个叉。