2016 CCPC Hangzhou Onsite(部分题)
D题 - Difference
题意:给你两个数$x$, $K$个数和一个公式$$f(y, K) = \sum_{z\text{ in every digits of }y} z^K (f(233, 2) = 2^2 + 3^2 + 3^2 = 22)$$
要求你找出所有满足$$x = f(y, K) - y$$这个式子的y的数量。
解决:折半枚举法,这个套路其实被用过很多遍,但以前没有研究过它,所以遇到这种题很少会往这个方向去想导致这种水题都难出。系统学习其实是自学的一种很好的途径,以后要形成这种学习意识才对。
废话说了这么多,我们来谈谈这个题。这个题知道方法后就很简单了,我们预处理低位对答案的贡献,然后枚举高位即可。所有位对答案的贡献其实就是$$\sum_{i=1}^{length} (num[i]^k - num[i]*10^i)$$
既然每位相对独立,折半枚举就不存在问题了。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 6 const int N = 9 + 5; 7 const int M = 100000; 8 9 map <ll, int> mp[N]; 10 int arry[N]; 11 ll pw[N][N]; 12 ll p10[N]; 13 14 void init() 15 { 16 for(int i = 0; i < N; ++ i) { 17 pw[i][0] = 1; 18 for(int j = 1; j < N; ++ j) 19 pw[i][j] = pw[i][j-1] * i; 20 } 21 p10[0] = 1; 22 for(int i = 1; i < N; ++ i) { 23 p10[i] = p10[i-1] * 10; 24 } 25 for(int i = 0; i < M; ++ i) { 26 int t = i, cnt = 0; 27 while(t) { 28 arry[cnt ++] = t % 10; 29 t /= 10; 30 } 31 for(int k = 1; k <= 9; ++ k) { 32 ll ans = 0; 33 for(int j = 0; j < cnt; ++ j) 34 ans += pw[arry[j]][k]; 35 mp[k][ans - 1ll * i] ++; 36 } 37 } 38 } 39 40 int main() 41 { 42 int T, kase = 1; 43 init(); 44 scanf("%d", &T); 45 while(T --) { 46 ll x, k; 47 ll ans = 0; 48 scanf("%lld%lld", &x, &k); 49 for(int i = 0; i < M; ++ i) { 50 int tmp = i; 51 ll sec = x; 52 while(tmp) { 53 sec -= pw[tmp % 10][k]; 54 tmp /= 10; 55 } 56 sec += p10[5] * i; 57 if(mp[k].count(sec)) 58 ans += 1ll * mp[k][sec]; 59 } 60 if(x == 0) ans --; 61 printf("Case #%d: %lld\n", kase ++, ans); 62 } 63 return 0; 64 }
J题 - Just a Math Problem
题意:给你个$n$,要求你求出
$$\sum_{i = 1}^{n} g(i)$$
其中
$$g(i)=2^{f(i)}$$
$f(i)$表示的是$i$的素因子的个数。
解决:此题有两种解法,都是值得大家去学习的。
解法一:利用莫比乌斯函数解决
莫比乌斯函数,他的符号为$\mu$,如果不知道这是什么那么可以把这种解法跳过,以后学习完这个概念后再来研究这种解法。
怎么用这个函数解决这个题呢?
首先我们看一下莫比乌斯函数的一个推导公式
$$\mu^{2}(x) = \sum_{k^2|x}\mu(k)$$
- 公式1
要这个有什么用呢?我们来分析题干上的公式,$g(i)=2^{f(i)}$,这里我们把它理解成$i$的无平方素因子的个数(仔细想想为什么),而$\mu(i)$只有在$i$无平方素因子才会有值,值为$1$, $-1$。所以我们可以将其转换成
$$g(i)=\sum_{d|i}\mu^{2}(d)$$
结合公式1我们得到
$$g(i)=\sum_{d|i}\sum_{k^2|d}\mu(k)$$
由整数的性质再结合题干公式我们得到
$$\sum_{i=1}^{n}=\sum\limits_{k=1}^{n}\mu(k)\sum_{k^2|d}\lfloor \frac{n}{d} \rfloor$$
至此,我们可以枚举$k$,当$k^2$大于n贡献就为0了我们就可以停止枚举,分块求出$$\sum_{k^2|d}\lfloor \frac{n}{d} \rfloor$$的值累加进答案即可。时间复杂度为$O(\sqrt{n}log(n))$。
这种方法有三难,难想,难推,难过。为什么会难过?嗯,这种方法被卡常卡的很厉害。你需要进行各种细节优化才能过。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 6 const int N = 1e6 + 5; 7 const ll MOD = 1e9 + 7; 8 9 int mu[N]; 10 int prime[N]; 11 ll g[N], mem[N]; 12 bool vis[N]; 13 int cn; 14 15 void init() 16 { 17 memset(mem, -1, sizeof(mem)); 18 cn = 0; 19 mu[1] = 1; 20 for(int i = 2; i < N; ++ i) { 21 if(!vis[i]) { 22 prime[cn ++] = i; 23 mu[i] = -1; 24 } 25 for(int j = 0; j < cn && i * prime[j] < N; ++ j) { 26 vis[i * prime[j]] = 1; 27 if(i % prime[j] != 0) { 28 mu[i * prime[j]] = - mu[i]; 29 } 30 else break; 31 } 32 } 33 } 34 35 void fadd(ll& a, ll b) 36 { 37 a += b; 38 if(a >= MOD) a -= MOD; 39 } 40 41 ll F(ll lim) 42 { 43 if(lim < N && mem[lim] != -1) return mem[lim]; 44 ll tmp = 0; 45 for(ll j = 1, st; j <= lim; j = st + 1) { 46 st = lim / (lim / j); 47 tmp += (st - j + 1) * (lim / j); 48 } 49 if(lim < N) mem[lim] = tmp % MOD; 50 return tmp % MOD; 51 } 52 53 int main() 54 { 55 init(); 56 int T, kase = 1; 57 scanf("%d", &T); 58 while(T --) { 59 ll n; 60 scanf("%lld", &n); 61 int lim = sqrt(n) + 1; 62 ll ans = 0; 63 for(int i = 1; i <= lim; ++ i) if(mu[i]){ 64 ans += mu[i] * F(n/i/i); 65 } 66 printf("Case #%d: %lld\n", kase ++, (ans % MOD + MOD) % MOD); 67 } 68 return 0; 69 }
在HDU上测得的状态:
解法二:利用互质关系解决
在逛博客时发现了一个更好的思路,这种解法对$g(i) = 2^{f(i)}$有另一种解释:乘积为$i$且互质的有序数对个数(再想想这是为什么)。
那么我们枚举$i$,再利用容斥计算下$[i+1, n/i]$中有多少个与其互质即可。计算出累加和$sum$还不够,因为是有序对,所以正真的答案为$ans = sum * 2 + 1$。
时间复杂度$O(能过)$
代码:
1 #include <bits/stdc++.h> 2 #define lowbit(x) ((x)&(-x)) 3 using namespace std; 4 5 typedef long long ll; 6 7 const int N = 1e6 + 5; 8 const ll MOD = 1e9 + 7; 9 10 int prime[N]; 11 ll mul[N]; 12 int L[N]; 13 bool vis[N]; 14 int cn = 0; 15 16 vector <int> vec[N]; 17 18 void init() 19 { 20 for(int i = 2; i < N; ++ i) { 21 if(!vis[i]) prime[cn ++] = i; 22 for(int j = 0; j < cn && prime[j] * i < N; ++ j) { 23 vis[prime[j] * i] = 1; 24 if(i % prime[j] == 0) break; 25 } 26 } 27 for(int i = 1; i < N; ++ i) 28 { 29 int cnt = 0, x = i; 30 for(int j = 0; j < cn; ++ j) { 31 if(prime[j] * prime[j] > x) break; 32 if(x % prime[j] == 0) { 33 vec[i].push_back(prime[j]); 34 } 35 while(x % prime[j] == 0) { 36 x /= prime[j]; 37 } 38 } 39 if(x != 1) vec[i].push_back(x); 40 } 41 L[1] = 0; 42 for(int i = 1; (1<<i) < N; ++ i) { 43 L[1<<i] = i; 44 } 45 } 46 47 ll cal(int x, ll n) 48 { 49 ll rtn = n - x; 50 mul[0] = 1; 51 for(int i = 1; i < (1<<(int)vec[x].size()); ++ i) { 52 int d = lowbit(i); 53 mul[i] = mul[i-d] * -vec[x][L[d]]; 54 rtn += n / mul[i] - x / mul[i]; 55 } 56 return rtn; 57 } 58 59 int main() 60 { 61 int T, kase = 1; 62 init(); 63 scanf("%d", &T); 64 while(T --) { 65 ll n, ans = 0; 66 scanf("%lld", &n); 67 for(int i = 1; 1ll * i * i <= n; ++ i) { 68 ans += cal(i, n / i); 69 } 70 printf("Case #%d: %lld\n", kase ++, (ans * 2 + 1) % MOD); 71 } 72 return 0; 73 }
在HDU测得的状态:
感谢观看,如有问题,欢迎在评论中指出。