今天軟院校賽,有一道H題非常的神,所以記下來。題意轉化了之后就是求歐拉函數的前綴和。自然的想法是O(n)的線性預處理可以求出前n個數的歐拉函數,又或者是O(sqrt(n))的預處理求出單個數的歐拉函數。但是題目要求的是前n(n<=10^9)個數歐拉函數的前綴和。於是我就覺得這是沒法做的了,賽后問了出題人,出題人非常好的給了下面的這個鏈接。
http://wzimpha.sinaapp.com/archives/596
然后大體的思路就在里面,這里也不重復了。主要是覺得這種方法好神,所以特別的在這里記下來,說不定以后用得上。
由於上面網址失效了,然后發現其實還是挺多人想知道的,於是我決定重看一下之前題目的代碼,重新更新一下這篇博文。首先先上題目:
H. Game
Alice likes to play games. One day she meets such a game. There are N * N switches arranged in an N * N array. Pressing a switch would change its state, from off to on or from on to off. In addition, if a switch at row r and column c is pressed, then all switches with coordinate (k * r, k * c) will change state, with integer k>1. Initially all switches are off.
For example, in the picture above, white buttons represent switches turned off and colored ones represent switches turned on. Initially all buttons are white. If the button at (2,2) is pressed, then buttons at (2,2), (4,4) will change state(represented with orange color). And if one presses the button (2,1), buttons at (2,1) and (4,2) will change from off toon(represented with gray color).
The goal of the game is to turn on all the switches (i.e. when you finish the game, all the switches must be at the state of on) and the player must do that with as few presses as possible. Now Alice would like your help.
Input
The first line of input file is an integer T, the number of test cases. T lines follow, each contain an integer N, the dimension of the array in one game.
1≤T≤50,1≤N≤10^9
Output
Output consists of T lines. Each line contains an integer, the minimum number of presses for the corresponding test case.
Sample input and output
Sample Input |
Sample Output |
2 2 3 |
3 7 |
Hint
For test case 1, press (1,1) (1,2) (2,1). For test case 2, press (1,1) (1,2) (1,3) (2,1) (2,3) (3,1) (3,2).
題意:給你一個n*n的矩陣的燈泡,當你按下(r,c)的燈泡的時候,所有的(k*r,k*c)的燈泡都會被點亮,問最小按的燈泡數使得所有的燈泡被點亮。
問題的模型可以轉換為 有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n)這是一個非常典型的gcd問題,如果我們令y<x,那么當x=k的時候,就有phi(k)個y使得 (x,y)=1,顯然x可以取遍1-n,所以答案是(phi(1)+phi(2)+...+phi(n))*2-1(假設phi(1)=1)
如果我們記M(n)為有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n)的個數。
那么有
有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n)的個數 ==> M(n)
有多少對(x,y)使得gcd(x,y)=2 (1<=x,y<=n)的個數 ==> 有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n/2)的個數 ==> M(n/2)
有多少對(x,y)使得gcd(x,y)=3 (1<=x,y<=n)的個數 ==> 有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n/3)的個數 ==> M(n/3)
....
有多少對(x,y)使得gcd(x,y)=n (1<=x,y<=n)的個數 ==> 有多少對(x,y)使得gcd(x,y)=1 (1<=x,y<=n/n)的個數 ==> M(n/n)
(以上的M/k都是整數除法)
如果我們把上面的式子的左邊加起來,再把右邊也加起來,我們會得到一個非常重要的等式,即:
n^2=M(n)+M(n/2)+...M(n/n) ==> sigma(M(n/i),i=1...n) = n*n
注意到 n/i 不同的答案只有O(sqrt(n))個,所以有很多M(n/i)其實是相等的,不需要重復計算,於是一個遞歸求解的思路就出來了。首先我先預處理出[1,10^6]以內的M(k),然后記憶化搜索,那個失效的鏈接里給出的一個結論就是這樣去計算的話復雜度是O(n^(2/3)),於是就解決了這個歐拉函數前綴和的問題。下面的代碼是出這道題的同學給出來的程序,我在這里也直接貼出來了。
#include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> #include <vector> #include <map> #include <math.h> #include <iomanip> #include <string> #include <set> #include <cassert> #include <queue> using namespace std ; #define rep(i,a,b) for(int i=(int)(a);i<(int)(b);++i) #define rrep(i,b,a) for(int i=(int)(b);i>=(int)(a);--i) #define clr(a,x) memset(a,(x),sizeof(a)) #define ll long long #define ull unsigned ll #define eps 1e-8 #define mp make_pair #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define ld long double const int N = 1e9; const int mod = 1313131; const int maxn = 1000000; ll key[mod], value[mod]; ll phi[maxn]; bool ispe[maxn]; void pre_init() { rep(i,1,maxn) phi[i] = i, ispe[i] = true; rep(i,2,maxn) if (ispe[i]) { phi[i] = i - 1; for(int j = i + i; j < maxn; j += i) { phi[j] = phi[j] / i * (i - 1); ispe[j] = false; } } rep(i,1,maxn) phi[i] += phi[i-1]; rep(i,1,maxn) phi[i] = 2 * phi[i] - 1; } void Add(ll n,ll val) { int k = n % mod; while (key[k] != -1) { ++k; if (k == mod) k = 0; } key[k] = n; value[k] = val; } ll Query(ll n) { int k = n % mod; while (key[k] != -1) { if (key[k] == n) return value[k]; ++k; if (k == mod) k = 0; } return -1; } ll dfs(ll n) { assert(n >= 1 && n <= N); if (n < maxn) return phi[n]; // else if (n == 2) return 3; // else if (n == 3) return 7; ll ret = Query(n); if (ret != -1) return ret; ret = n * n; for(int x = 2, y; x <= n; ++x) { y = n / (n / x); ret -= dfs(n / x) * (y - x + 1); x = y; } Add(n,ret); return ret; } void Getinput() { freopen("in.txt","w",stdout); int T = 50; printf("%d\n",T); int x = 1; int * a = new int[T]; rep(i,0,T) { a[i] = x; x += N / (rand()%50 + 1); x = x % N + 1; } random_shuffle(a,a+T); a[rand()%T] = N; rep(i,0,T) printf("%d\n",a[i]); delete [] a; } int main() { //Getinput(); return 0; #ifdef ACM freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); freopen("fast_out.txt","w",stdout); #endif // ACM pre_init(); clr(key,-1); int T; cin >> T; while (T--) { int n; scanf("%d",&n); assert(n >= 1 && n <= N); printf("%lld\n",dfs(n)); } return 0; }