1. 程式人生 > >[SDOI2008] 儀仗隊

[SDOI2008] 儀仗隊

algorithm 坐標 不能 def 記得 min long 線段 ref

題目類型:莫比烏斯反演/歐拉函數??

傳送門:>Here<

題意:有一個\(N*N\)的方陣,問左下角那個人可以看到幾個人?(光沿直線傳播)

解題思路

考慮什麽時候一個人會看不見?那就是和別的一條視線重合的時候。又由於線段的另一個端點是確定的,因此視線會重合當且僅當

斜率相同。

因此題目就是在問我們有幾個不同的斜率。設一個人的位置為\((i,j)\),則他的斜率是\(\dfrac{j}{i}\)。要使斜率不同,其實也就

是此分數不得被約分。為什麽?因為如果能被約分就對應另外一個人的坐標了!

因此題目也就是轉化為求在\(N\)的範圍內互質整數對的個數(互質則不能約分)。因此莫比烏斯反演

即可

然而我們默認了原點是\((0,0)\),因此先將那塊右上角求好,讓現在的\((1,1)\)去當\((0,0)\),然後加上\((0,1)\)\((1,0)\)則兩個

點。註意,這時候我們已經把\(N\)減了1了。

反思

這題的關鍵在於想怎樣會看不到,而不是怎樣才能看到。有時正著想不出來,反著想更簡單

另外,考慮邊界條件的時候常常涉及到+1-1,這時候要大膽猜想,小心驗證。

Code

篩莫比烏斯函數的時候要記得判斷超界。(就是這樣\(RE\)的)

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 400010;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
int N,ans;
int mu[MAXN],prime[MAXN],b[MAXN],tot;
inline void getMobius(){
    mu[1] = 1;
    for(int i = 2; i <= N; ++i){
        if(!b[i]){
            prime[++tot] = i;
            mu[i] = -1;
        }
        for(int j = 1; j <= tot; ++j){
            if(i * prime[j] > N) break;
            b[i * prime[j]] = 1;
            if(i % prime[j] == 0){
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}
int main(){
    N = read()-1;
    if(N <= 0){
        printf("0");
        return 0;
    }
    getMobius();
    for(int i = 1; i <= N; ++i){
        ans += mu[i] * (N/i) * (N/i);
    }
    printf("%d", ans+2);
    return 0;
}

[SDOI2008] 儀仗隊