t1:给你一个数n,让你统计一下从1到n所有数与n的最大公约数都有哪些,分别有多少个。
简单推一下样例,容易发现,除了互质的数以外,其他的与n的最大公约数一定是n的因子,个数应该是φ(n/gcd)。那么我们要做的应该是先把n的因数全部找出来,然后对每个因数质因数分解求φ。如果直接粗略的算复杂度,应该是sqrt(n)^2的。这样就是n了,但n是1e13啊!
所以我们大致算一下对于一个数来说,它的因数能有多少个。对于一个数来说,它的因数个数应该是质因数分解之后,每个质因数的幂+1的乘积。我们如果想让一个数在某一个范围内,然后因数尽可能多的话,那么它的质因数的幂肯定是单调不增的。我上午考试的时候推了一下,2*3*5*7*11*13*17*19*23*29*31*37=66786643213290,是6e13的。它的因数个数也就是2^12个。不可能再比这个大了,因为2的幂次方增长是指数级的,不会为了前面增长个几然后把后面的那个2舍弃的。这个也就是4000,然后你每次质因数分解复杂度是sqrt(x),最大是3e6的样子,但肯定远小于这个值,所以足以通过此题。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e7+10;
ll n,a[maxn];
int m,p[maxn],num;
void put(ll x)
{
if(x==0){putchar('0');return;}
if(x==1){putchar('1');return;}
if(x<0){putchar('-');x=-x;}
int num=0;char ch[25];
while(x) ch[++num]=x%10+'0',x/=10;
while(num) putchar(ch[num--]);
//putchar('\n');
}
int main(){
freopen("gcd.in","r",stdin);
freopen("gcd.out","w",stdout);
scanf("%lld",&n);
for(ll i=2;i*i<=n;++i){
if(n%i==0){
a[++num]=i;
if(i*i!=n) a[++num]=n/i;
}
}
sort(a+1,a+num+1);
a[0]=1;
for(int i=0;i<=num;i++){
ll x=n/a[i];
m=0;
for(ll j=2;j*j<=x;++j){
if(x%j==0) p[++m]=j;
while(x%j==0) x/=j;
}
if(x>1) p[++m]=x;
ll temp=n/a[i];
for(int k=1;k<=m;k++) temp=temp*(p[k]-1)/p[k];
//printf("%lld %lld\n",a[i],temp);
put(a[i]),putchar(' '),put(temp),putchar('\n');
}
printf("%lld 1\n",n);
return 0;
}
t2:一个不太好像的递推吧。给你一个环,环上有n个点,然后任意k个连续点中你只能选一个点或者不选,求方案数。
我一般比较怕这种题,每次看到之后,总感觉太多需要考虑的东西,好多情况需要考虑,然后怎么不会漏算,怎么不会重复计算。然后就会觉得应该是个组合数,容斥什么的,就不想写了。其实是把问题想复杂化了,就是一个递推。
在k=2的时候递推很好想,直接设个状态,f[i][0]表示没使用i的方案数,f[i][1]表示使用了i的方案数。然后往下推,最后考虑一下环,所以我们依次假设第一个点使用和没使用,答案求和就好了。
再推广到k<=1e5的情况。假如没有环,我们发现当前点i取或不取,只与i-k取或不取有关。如果当前点一定取,那么我们就是方案数加上之前递推到i-k的方案数,从1到i-k可以任意,不影响当前i。如果不取i,我们直接加上递推到i-1的方案数就行。所以我们仍然是定义两个数组,f【i】表示从1到i一定使用的i的方案数,g【i】表示从1到i的方案数,可以发现,g其实就是f的一个前缀和。但是环怎么处理呢?我们发现,从1到n-k+1这些点都是不受环的影响的,然后之后我们每次往后移动一个点,左端点其实就得向右移动一个点。这样的话,我们每移动一次,加上的方案数就是从左端点到右端点一定使用右端点的方案数,这样既枚举到了所有情况,也没有重复计算,因为当前新的点一定使用的情况下,一定和前面的不重复。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int mod=1e9+7;
int n,k,f[N],g[N];
int main(){
scanf("%d%d",&n,&k);
g[1]=f[1]=1;
for(int i=2;i<=n-k+1;i++){
g[i]=g[i-1];
if(i-k>0) g[i]=(g[i]+g[i-k])%mod,f[i]=g[i-k];
f[i]++;g[i]++;
f[i]%=mod;g[i]%=mod;
}
for(int i=n-k+2;i<=n;i++) g[i]=(g[i-1]+f[n-k+1])%mod;
printf("%d\n",g[n]);
return 0;
}