题目链接
题意
有一个 n 行m 列(1≤n,m≤300)的点阵,问一共有多少条非水平非竖直的直线至少穿过其中两个点?如下图所示,n=2, m=4 时答案为12,n=m=3 时答案为14。
分析
时间复杂度从的一个优化过程。
设f[m][n]为m列n行时的答案,c[m][n]为长度为m(有m+1列)且宽度不超过n(最多n+1行)时与m互质的计数。
c[m][n]可以在内求出,其中判断两数是否互质的方法复杂度为:
bool coprime(int a, int b) {
if (a > b) return coprime(b, a);
if (a == 0) return b==1;
if (a & 1) {
if (b & 1) return coprime(a, (b-a)>>1);
return coprime(a, b>>1);
} else if (b & 1) return coprime(a>>1, b);
return false;
}
for (int i=1; i<N; ++i) for (int j=1; j<N; ++j) c[i][j] = 2*coprime(i, j) + c[i][j-1];
基于c[m][n]可以写出一个的求解算法:
for (int i=2; i<N; ++i) for (int j=2; j<N; ++j) {
f[i][j] = f[i][j-1];
for (int k=1; k<i; ++k) {
for (int x=k/2+1; x<=k; ++x) f[i][j] += c[x][j-1];
for (int x=k/2; x>0; --x) f[i][j] += c[x][j-1] - c[x][(j-1)/2];
}
}
对于的规模,以上递推算法提交后卡在1s内通过,可以优化得到算法:
for (int i=2; i<N; ++i) for (int j=2; j<N; ++j) {
f[i][j] = f[i-1][j];
for (int k=1; k<i; ++k) f[i][j] += c[k][j-1];
for (int k=(i-1)/2; k>0; --k) f[i][j] -= c[k][(j-1)/2];
}
for (int i=2; i<N; ++i) for (int j=2; j<N; ++j) f[i][j] += f[i][j-1];
对于的规模,算法时间开销已经足够小了,但其实还可以优化到:
for (int i=1; i<N; ++i) for (int j=1; j<N; ++j)
f[i+1][j+1] = 2*f[i][j+1] - f[i-1][j+1] + c[i][j] - (i&1 ? 0 : c[i/2][j/2]);
for (int i=2; i<=N; ++i) for (int j=2; j<=N; ++j) f[i][j] += f[i][j-1];
由于求c[m][n]的时间复杂度为,所以最终的时间复杂度为。
AC代码
#include <iostream>
using namespace std;
#define N 305
int f[N+1][N+1] = {0}, c[N+1][N+1] = {0};
bool coprime(int a, int b) {
if (a > b) return coprime(b, a);
if (a == 0) return b==1;
if (a & 1) {
if (b & 1) return coprime(a, (b-a)>>1);
return coprime(a, b>>1);
} else if (b & 1) return coprime(a>>1, b);
return false;
}
int main() {
for (int i=1; i<N; ++i) for (int j=1; j<N; ++j) c[i][j] = 2*coprime(i, j) + c[i][j-1];
for (int i=1; i<N; ++i) for (int j=1; j<N; ++j)
f[i+1][j+1] = 2*f[i][j+1] - f[i-1][j+1] + c[i][j] - (i&1 ? 0 : c[i/2][j/2]);
for (int i=2; i<=N; ++i) for (int j=2; j<=N; ++j) f[i][j] += f[i][j-1];
int m, n;
while (cin>>n>>m && n) cout << f[m][n] << endl;
return 0;
}