题面
80pts考场做法:
考虑每个数在矩形中的出现次数。x的出现次数为max(0,x的<=m的约数个数+x的<=n的约数个数-x的约数个数),不妨设n>=m,而且询问的k<=max(n,m)=n,x肯定也<=n。所以x的出现次数即为x的<=m的约数个数。把每个数的约数表预处理出来,再搞个前缀和什么的。对于每个询问二分查找lower_bound一下即可。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,q,pri[100010],num,d[1000010],bg[1000010],ed[1000010],minp[1000010],s[1000010];
bool flag[1000010];
void getpri()
{
memset(flag,1,sizeof(flag));
flag[1]=0;
for(int i=2;i<=n;i++)
{
if(flag[i]) pri[++num]=i,minp[i]=1;
for(int j=1;j<=num&&i*pri[j]<=n;j++)
{
int v=i*pri[j];
flag[v]=0;
minp[v]=pri[j];
if(i%pri[j]==0) break;
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
if(n<m)swap(n,m);
getpri();
int tot;
bg[1]=ed[1]=d[1]=s[1]=tot=1;
for(int i=2;i<=n;i++)
{
tot++;
bg[i]=ed[i-1]+1;ed[i]=ed[i-1];
int c=i/minp[i];
if(flag[i]) {d[++ed[i]]=1;if(i<=m) d[++ed[i]]=i;}
else
{
int p,q;
p=q=bg[c];
while(p<=ed[c]||q<=ed[c])
{
if(p>ed[c]||(d[p]>d[q]*minp[i]))
{
if(d[q]*minp[i]>m) break;
if(c%(d[q]*minp[i])!=0)d[++ed[i]]=d[q]*minp[i];
q++;
}
else
{
if(d[p]>m) break;
d[++ed[i]]=d[p];
p++;
}
}
}
s[i]=s[i-1]+ed[i]-bg[i]+1;
if(s[i]>n) break;
}
while(q--)
{
int x,ans;
scanf("%d",&x);
ans=lower_bound(s,s+tot+1,x)-s;
printf("%d\n",ans);
}
return 0;
}
100pts做法:设n>=m,二分答案x,矩阵中小于等于x的数显然为∑x/i,枚举除法搞一搞。O(qlognsqrt(n)),卡卡常即可。
(我给询问排了个序。。。上一个的答案直接作为下一个的二分下届。。。)
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,q;
struct node
{
int k,id,ans;
}a[110];
bool cmp1(node a,node b){return a.k<b.k;}
bool cmp2(node a,node b){return a.id<b.id;}
ll cal(int x)
{
ll re=0;
int p,d;
for(p=1;p<=min(m,x);p=x/d+1)
{
d=x/p;
re+=(x/d-p+1)*d;
}
re+=(m-p+1)*(x/p);
return re;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
if(n<m) swap(n,m);
for(int i=1;i<=q;i++)
{scanf("%d",&a[i].k);a[i].id=i;}
sort(a+1,a+q+1,cmp1);
int l=0,r;
for(int i=1;i<=q;i++)
{
r=a[i].k;
while(l<r)
{
int mid=(l+r)>>1;
if(cal(mid)<a[i].k) l=mid+1;
else r=mid;
}
a[i].ans=l;
}
sort(a+1,a+q+1,cmp2);
for(int i=1;i<=q;i++)
printf("%d\n",a[i].ans);
return 0;
}