问题:
Up the Strip (simplified version),Up the Strip(Codeforces Round #740)
题意:
给出一个数n,求把n变成1的方案数,对m(m是一个质数)取模。
每次有两种操作,设x为当前数字。
- 选择1 ≤ \leq ≤k ≤ \leq ≤x-1,使x=x-k;
- 选择2 ≤ \leq ≤k ≤ \leq ≤x,使x= ⌊ x / k ⌋ \lfloor x/k \rfloor ⌊x/k⌋;
数据范围:
easy version中2
≤
\leq
≤n
≤
\leq
≤ 2×
1
0
5
10^5
105,
1
0
8
10^8
108
≤
\leq
≤m
≤
\leq
≤
1
0
9
10^9
109
hard version中2
≤
\leq
≤n
≤
\leq
≤ 4×
1
0
6
10^6
106,
1
0
8
10^8
108
≤
\leq
≤m
≤
\leq
≤
1
0
9
10^9
109
思路:
可以看出来,把n变成1的方案数,就是把1变成n的方案数。两重for循环得到a[n]即可,时间复杂度O( n 2 n^2 n2)
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN=4000010;
int a[MAXN];
int f(int n,int MOD){
a[1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=i-1;j++){
a[i]=(a[i]+a[j])%MOD;
}
for(int j=2;j<=i;j++){
a[i]=(a[i]+a[i/j])%MOD;
}
}
return a[n];
}
int main(){
int n,m;
cin>>n>>m;
cout<<f(n,m);
return 0;
}
结果,到了第四个点,不出所料地TLE了。。。
考虑优化。
第二重for循环,前面一项 ∑ j = 1 i − 1 a [ i ] \sum_{j=1}^{i-1} {a[i]} ∑j=1i−1a[i]可以用前缀和优化,每次加上sum[i-1]即可;后面一项,每次都是向下取整,真正的除数只有 i \sqrt[]{i} i个,可以用整除分块来做,总体时间复杂度O(n n \sqrt[]{n} n),2× 1 0 5 10^5 105数据没问题,跑满1秒 1 0 8 10^8 108次运算。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN=4000010;
// int a[MAXN];
// int f(int n,int MOD){
// a[1]=1;
// for(int i=2;i<=n;i++){
// for(int j=1;j<=i-1;j++){
// a[i]=(a[i]+a[j])%MOD;
// }
// for(int j=2;j<=i;j++){
// a[i]=(a[i]+a[i/j])%MOD;
// }
// }
// return a[n];
// }
int a[MAXN],sum[MAXN];
int f(int n,int MOD){
a[1]=1;
sum[1]=1;
for(int i=2;i<=n;i++){
for(int l=1,r;l<=i;l=r+1){
r=i/(i/l);
a[i]=(a[i]+1ll*(r-l+1)*a[i/l])%MOD;
}
a[i]=(a[i]+sum[i-1])%MOD;
sum[i]=(sum[i-1]+a[i])%MOD;
}
return a[n];
}
int main(){
int n,m;
cin>>n>>m;
cout<<f(n,m);
return 0;
}
然而,在hard version的2 ≤ \leq ≤n ≤ \leq ≤ 4× 1 0 6 10^6 106数据下,在第四个点就TLE了
继续考虑优化。
把1变成n的方案优化不动了,考虑把n变成1的方案。这个时候a[k]的意义就是把n变成k的方案数。
每次枚举除数j,也就是i可以从[ji,ji+j-1]除以j得到,时间复杂度为O(n×( 1 2 \frac{1}{2} 21+ 1 3 \frac{1}{3} 31+ 1 4 \frac{1}{4} 41+…+ 1 n \frac{1}{n} n1)),即O(n l o g n log_n logn),4× 1 0 6 10^6 106数据没问题。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN=4000010;
// int a[MAXN];
// int f(int n,int MOD){
// a[1]=1;
// for(int i=2;i<=n;i++){
// for(int j=1;j<=i-1;j++){
// a[i]=(a[i]+a[j])%MOD;
// }
// for(int j=2;j<=i;j++){
// a[i]=(a[i]+a[i/j])%MOD;
// }
// }
// return a[n];
// }
// int a[MAXN],sum[MAXN];
// int f(int n,int MOD){
// a[1]=1;
// sum[1]=1;
// for(int i=2;i<=n;i++){
// for(int l=1,r;l<=i;l=r+1){
// r=i/(i/l);
// a[i]=(a[i]+1ll*(r-l+1)*a[i/l])%MOD;
// }
// a[i]=(a[i]+sum[i-1])%MOD;
// sum[i]=(sum[i-1]+a[i])%MOD;
// }
// return a[n];
// }
int a[MAXN],sum[MAXN];
int f(int n,int MOD){
a[n]=1;
sum[n]=1;
for(int i=n-1;i>=1;i--){
a[i]=sum[i+1];
for(int j=2;j*i<=n;j++){
int r=min(n,j*i+j-1);
a[i]=(1ll*a[i]+sum[j*i]-sum[r+1]+MOD)%MOD;
}
sum[i]=(sum[i+1]+a[i])%MOD;
}
return a[1];
}
int main(){
int n,m;
cin>>n>>m;
cout<<f(n,m);
return 0;
}
函数里,有一个地方需要注意
sum[i]=(sum[i+1]+a[i])%MOD;
//这里的sum[i]由于取模,可能会使后面的sum[j*i]<sum[r+1],要在"sum[j*i]-sum[r+1]"后面加上MOD保证a[i]为正数
a[i]=(1ll*a[i]+sum[j*i]-sum[r+1]+MOD)%MOD;
//这里四个int型的数相加,MOD的范围是1e9,所以可能会发生溢出,要在前面乘以1ll
一个问题,三个相同名字相同形参的函数。
所用的空间相同,但第三个函数跑一秒就能解决的问题第一个函数得跑1个小时。
或许,这就是算法的魅力吧。