欧拉函数前缀和
今天软院校赛,有一道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; }