题目
t(t<=10)组样例,每次给定n,m1,m2(1<=n,m1,m2<=1e9)
表示有一个n*n的方阵,格子(i,j)(1<=i,j<=n)上写的数是i*j
令m=m1*m2,对于m的每一个因子x,
记方阵中出现x的最小的行数为row[x](1<=row[x]<=n),若未出现,则row[x]=0
需要输出满足row[x]>0的个数cnt,以及所有row[x]的异或和xor
思路来源
rainboy代码
题解
先求出m的所有因子,然后考虑dp
i*j=x,i越小越好,则j越大越好,dp[x]记录x最大的不超过n的因子j
转移枚举x的所有质因子p,dp[x]从dp[x/p]转移而来
其实dp直接维护最小的i也可以,只是在转移的时候,
需要考虑本次能否将p分配给j(i能不变尽量不变),
不能分(j*p>n)的话再考虑能否将p分配给i,
再不能分(i*p>n)的话,就无解了,略麻烦一步
心得
赛中用正解配合map搞过去了,2.5s的题跑了2s,
一看rainboy提交,62ms,还是来学习一下吧
一些学到的tips
1. 如何确定数组开多大?
https://oeis.org/A066150 ,x位数最大的因子个数,18位数对应103680
2. 如何用memset变长初始化?
memset时指定个数,就可以起到手动初始化的作用
memset(dp,-1,d[c]*sizeof *dp);
3. 如何预处理所有因子?
根据约数定理,
对于,其约数个数
,
考虑新增一个数的增量贡献,
实际是
记
因此,可以令区间中下标
对应的数
,
从通过新增一个素因子
转移而来
for(int i=0;i<c;++i){
d[i+1]=d[i]*(num[i]+1);
for(int j=d[i];j<d[i+1];++j){
v[j]=v[j-d[i]]*p[i];
}
}
4. 如何判断每个因子有哪些质因子?
根据刚才的转移方式,每个长为的分区,
前个是不包含
的,后面的才是包含
的,
所以,遍历个质因子时,对于下标
对应的数
,
只有落到后半段,才表明
包含这个质因子
,需要从
转移而来
for(int i=0;i<c;++i){
for(int j=0;j<d[c];++j){
if(j%d[i+1]>=d[i]){
dp[j]=max(dp[j],dp[j-d[i]]);
}
}
}
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int N=103680;// https://oeis.org/A066150
#define fi first
#define se second
map<int,int>fac;
int t,n,m1,m2,cnt;
int p[N],num[N],d[N],c;
ll dp[N],v[N],xo;
void f(int x){
for(int i=2;1ll*i*i<=x;++i){
while(x%i==0)x/=i,fac[i]++;
}
if(x>1)fac[x]++;
}
int main(){
cin>>t;
while(t--){
cin>>n>>m1>>m2;
f(m1);f(m2);
for(auto &x:fac){
p[c]=x.fi;
num[c++]=x.se;
}
v[0]=d[0]=1;
for(int i=0;i<c;++i){
d[i+1]=d[i]*(num[i]+1);
for(int j=d[i];j<d[i+1];++j){
v[j]=v[j-d[i]]*p[i];
}
}
memset(dp,-1,d[c]*sizeof *dp); // 指定个数memset
for(int i=0;i<d[c];++i){
if(v[i]<=n)dp[i]=max(dp[i],v[i]);
}
for(int i=0;i<c;++i){
for(int j=0;j<d[c];++j){
if(j%d[i+1]>=d[i]){
dp[j]=max(dp[j],dp[j-d[i]]);
}
}
}
for(int i=0;i<d[c];++i){
if(dp[i]==-1)continue;
if(v[i]/dp[i]<=n){
cnt++;
xo^=v[i]/dp[i];
}
}
cout<<cnt<<" "<<xo<<endl;
cnt=xo=c=0;
fac.clear();
}
return 0;
}