题解 P2158 【[SDOI2008]仪仗队】

· · 题解

第一眼看到这题,哇塞似曾相识

仔细一想,这是P1447能量采集的缩小版和简化版

于是,我就用着过P1447的代码模板稍微改了一下

思路是这样的:

比如n = 5

* . . . .

C * - - -

不难发现,标星号的两个点无论n是几都能看到,而且都看不到标减号的位置

那么问题就是剩下的标小点点的位置,也就是一个n-1边长的矩阵 ps:这里就和能量采集差不多了

为剩下的矩阵重新定义坐标,并标出能看到的:

4 o . o .

3 o o . o

2 o . o .

1 o o o o

c 1 2 3 4

若被标注点的坐标是(x,y)的话,很容易发现,如果gcd(x,y)=1,就满足

那就只用数数x,y<=n且gcd(x,y)=1的个数就好啦

怎么数呢?

首先假定所有的x,y都满足,然后开始筛

仍然很容易发现,gcd(x,y)=m(m<=n)的个数为(n/m)^2向下取整的结果

令q[i]为gcd(x,y)=i的个数,也就是说,我们要求的是q[1]

gcd(x,y)=4的(也就是等于n的,之后往下依次递减)有1个(也就是(n/4)*(n/4)个),则q[4]=1,但为了避免之后重复计算,要将4的因数的q值都减q[4],即q[1]--,q[2]--

gcd(x,y)=3的有1个(也就是(n/3)*(n/3))个,同样的,q[1]--

q[2]=(n/2)*(n/2)=4,但因为其中有一个被q[4]减去了,所以q[2]=3,同样q[1]-=3

那么,q[1]=(n/1)*(n/1)-q[4]-q[3]-q[2]=15,右上角的矩阵就处理完了

然后加上一开始扔掉的两个,15+2=17就是最终答案

时间复杂有点高,枚举一遍n,对于每个i都求一遍因数,那么时间复杂度就是n*sort(n)

#include <iostream>
#include <cmath>
#define ll long long
using namespace std;
ll q[100100] = {0}; //q[i]表示gcd(x,y)=i的个数
int main(void)
{
    ll n;
    cin >> n; n--; //直接处理右上角的那个子矩阵,所以n--
    for (ll i = n; i >= 1; i--)
        q[i] = ((n / i) * (n / i)); //预处理q[i],然后在下面筛去重复的
    for (ll i = n; i >= 2; i--) {
        for (ll j = 2; j*j <= i; j++) //寻找i的因数
            if (!(i % j) && i != j) { //如果j是i的因数,则q[j]-=q[i]
                q[j] -= q[i];
                if (j != i/j) q[i/j] -= q[i]; //这个很好理解,比如i=10的时候只枚举1~3,所以在j=2的时候就要筛掉q[5]
            }
        q[1] -= q[i];
    }
    cout << q[1]+2; //最后加上2
    return 0;
}

另外附上P1447的代码:

#include <iostream>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
ll q[100100] = {0};
int main(void)
{
    ll n, m;
    cin >> n >> m;
    ll minn = min(n, m);
    ll ans = 0;
    for (ll i = minn; i >= 1; i--)
        q[i] = ((n / i) * (m / i));
    for (ll i = minn; i >= 2; i--) {
        for (ll j = 2; j*j <= i; j++)
            if (!(i % j) && i != j) {
                q[j] -= q[i];
                if (j != i/j) q[i/j] -= q[i];
            }
        q[1] -= q[i];
    }
    for (ll i = 1; i <= minn; i++) ans += q[i]*i;
    cout << ans*2-(ll)n*m;
    return 0;
}