https://ac.nowcoder.com/acm/contest/9510
B - 最小的指数
题意:T,1≤T≤100000 次询问,每次给定数 n,1≤n≤1018,令 n=p1a1p2a2⋯pkak,a1,a2,⋯,ak>0,问 min{a1,a2,⋯,ak}。
因数分解
对于这种 n 很大的题目,如果要分解质因数,首先就应该考虑能否只枚举一个小范围内的质因子。
枚举 [2,5000] 的质数,暴力分解,最终 n 只包含 >5000 的质因子。
如果 ans=4,说明 n 中存在 p4,且其它因子的幂次都 ≥4,但是如果有两个质因子,这就不可能发生,所以只可能有 n=p4,因此需要判断 n 的四次根号是否为整数。
如果 ans=3,与上面相同,只可能是 n=p3,因此需要判断 n 的三次根号是否为整数。
如果 ans=2,可能有 n=p2 或者 n=p12⋅p22,这时只要判断 n 是否是完全平方数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <bits/stdc++.h> #define debug(x) cout << #x << ":\t" << (x) << endl; using namespace std; #define ll long long #define ull unsigned ll const int N = 2e6 + 10; const int INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = 1e9 + 7; int prime[N], vis[N], tot; void sieve(int n) { for (int i = 2; i <= n; i++) { if (!vis[i]) { prime[++tot] = i; } ll d; for (int j = 1; j <= tot && (d = i * prime[j]) <= n; j++) { vis[d] = 1; if (i%prime[j] == 0)break; } } } ll Pow(ll x, ll b) { ll res = 1; for (int i = 0; i < b; i++)res *= x; return res; } int T; int main() { sieve(5000); scanf("%d", &T); while (T--) { ll n; scanf("%lld", &n); if (n == 1) { puts("0"); continue; } int ans = INF; for (int i = 1; i <= tot; i++) { if (n%prime[i] == 0) { int cnt = 0; while (n%prime[i] == 0) { cnt++; n /= prime[i]; } ans = min(ans, cnt); } } if (n != 1) { ll x = llround(pow(1.0*n, 0.25)); bool flg = 0; for (int i = -10; i <= 10; i++) { if (x*x*x*x == n) { flg = 1; break; } } if (flg) { printf("%d\n", min(ans, 4)); continue; }
x = llround(pow(1.0*n, 1.0 / 3.0)); for (int i = -10; i <= 10; i++) { if (x*x*x == n) { flg = 1; break; } } if (flg) { printf("%d\n", min(ans, 3)); continue; }
x = llround(pow(1.0*n, 0.5)); for (int i = -10; i <= 10; i++) { if (x*x == n) { flg = 1; break; } } if (flg) { printf("%d\n", min(ans, 2)); continue; } printf("1\n"); } else printf("%d\n", ans); } return 0; }
|
C - 排列
题意:有一个 n 的排列,超级逆序对数为 K,问猜中这个排列的概率。超级逆序对:满足 i<j 且 ai>aj+1 的二元组 (i,j)。
dp
dp[i][j][k] 表示前 i 个数,有 j 个超级逆序对,且 1 的位置在 k 的方案数。
可以先考虑普通的逆序对。dp[i][j] 表示前 i 个数,有 j 个超级逆序对的方案数。
前 i 个数的情况由前 i−1 个数时的情况转移得到,把前 i−1 个数都 +1,则原本的逆序对数不变,现在要插入数字 1,插在位置 k,则新增 k−1 个逆序对,虽然我们要枚举 1 的位置,但是不需要记录下来。这样只要二维dp就能搞定。
再考虑超级逆序对,前 i−1 个数转移到前 i 个数时我们把所有数+1,再插入 1,但是这时新增逆序对数不等于 k−1,因为 (2,1) 这时不算逆序对了。所以还要记录前 i−1 个数时 1 的位置。所以dp增加一维。
dp[i][j][k]=p=1∑k−1dp[i−1][j−k+2][p]+p=k∑i−1dp[i−1][j−k+1][p]
发现可以前缀和优化掉两个 ∑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <bits/stdc++.h> #define debug(x) cout << #x << ":\t" << (x) << endl; using namespace std; #define ll long long #define ull unsigned ll const int N = 2e6 + 10; const int INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = 998244353; int n, K; ll Pow(ll a, ll b) { ll res = 1; while (b) { if (b & 1)res = res * a%mod; b >>= 1; a = a * a%mod; } return res; } ll dp[510][510], tmp[510][510];
int main() { scanf("%d%d", &n, &K); dp[0][1] = 1; for (int i = 2; i <= n; i++) { for (int j = 0; j <= min(i*(i - 1) / 2, K); j++) { for (int k = 1; k <= i; k++) { tmp[j][k] = dp[j][k]; dp[j][k] = 0; } } for (int j = 0; j <= min(i*(i - 1) / 2, K); j++) { for (int k = 1; k <= i; k++) { if (j - k + 2 >= 0)dp[j][k] = (dp[j][k] + tmp[j - k + 2][k - 1]) % mod; if (j - k + 1 >= 0)dp[j][k] = (dp[j][k] + tmp[j - k + 1][i - 1] - tmp[j - k + 1][k - 1] + mod) % mod; } for (int k = 1; k <= i; k++)dp[j][k] = (dp[j][k] + dp[j][k - 1]) % mod; } } printf("%lld\n", Pow(dp[K][n], mod - 2)); return 0; }
|
D - 数列
题意:给定一个数组,问多少个区间满足:区间中每个数都出现了 k 的倍数次。
哈希
记录在 [1,i] 中 x 出现的次数 cnt[i][x],则区间 [i,j] 满足条件等价于 cnt[i−1][1]≡cnt[j][1]modk,cnt[i−1][2]≡cnt[j][2]modk,⋯。
对 {cnt[i][1]%k,cnt[i][2]%k,⋯} 哈希,则区间左右端点的哈希值必须相同。
哈希方式很多,比如题解里是给每个数字随机一个值,或者每个数字分配一个幂次 pw[i],但是这个 pw[i] 最好怪一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <bits/stdc++.h> #define debug(x) cout << #x << ":\t" << (x) << endl; using namespace std; #define ll long long #define ull unsigned ll const int N = 2e6 + 10; const int INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; #define random(a,b) uniform_int_distribution<int> ((a), (b))(mt) mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count()); int n, k; int a[N], cnt[N]; map<ll, int>cc; ll hsh[N], ans, hah[N]; int main() { for (int i = 1; i < N; i++)hah[i] = random(10212, 219320329); scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++)scanf("%d", &a[i]); for (int i = 1; i <= n; i++) { cnt[a[i]]++; hsh[i] = hsh[i - 1] - (cnt[a[i]] - 1) % k*hah[a[i]] + cnt[a[i]] % k*hah[a[i]]; } cc[0]++; for (int i = 1; i <= n; i++) { ans += cc[hsh[i]]; cc[hsh[i]]++; } printf("%lld\n", ans); return 0; }
|
E - 反演
题意:定义 F(m,n)=i=1∑nj∣m∑σ0(ij)。其中 σ0(n)=i=1∑n[n%j==0],即 n 的因子个数。给定 n,m,求 F(m!,n),1≤n≤1011,1≤m≤100。
莫比乌斯反演+推式子
先来看 F(m,n)。
F(m,n)=i=1∑nj∣m∑σ0(ij)
i⋅j 的因子都可以分解成 x⋅y,其中 x∣i,y∣j,为了保证不重复统计,必须还要有 gcd(x,y)=1。
所以
F(m,n)=i=1∑nj∣m∑x∣i∑y∣j∑[gcd(x,y)=1]
改变次序,把 x,y 放到最前面。
这时就要考虑对于每一对 (x,y),它们被统计了几次。
x 被 i=x,2x,3x,⋯⌊xn⌋x 时统计到,y 被 y∣j,j∣m 时统计到。x 的比较简单,y 对应的 j 的个数其实是 σ0(ym),也就是 j 在 y 的基础上还能乘哪些数使得仍有 j∣m。
所以
F(m,n)=x=1∑ny∣m∑[gcd(x,y)=1]⋅⌊xn⌋⋅σ0(ym)
莫比乌斯反演得到
F(m,n)=x=1∑ny∣m∑d∣gcd(x,y)∑μ(d)⋅⌊xn⌋⋅σ0(ym)=x=1∑n⌊xn⌋y∣m∑σ0(ym)d∣gcd(x,y)∑μ(d)
把 d 放到最前面
同样要考虑同一个 d 被统计了几次。每次 x 都为 d 的倍数,而对于每个 x 都乘了一个 ⌊xn⌋,x=d 时为 dn,x=2d 时为 2dn,x=3d 时为 3dn,……,所以总贡献为 i=1∑dni⋅dn,记为 S(dn).
F(m,n)=d∣m,d<n∑μ(d)S(dn)d∣y,y∣m∑σ0(ym)
下面再考虑 F(m!,n)。
即
F(m!,n)=d∣m!,d<n∑μ(d)S(dn)d∣y,y∣m!∑σ0(ym!)
把 d 放在最前面的用处就在于只有质因子最高幂次为 1 的 d,才产生贡献,所以可以暴力dfs枚举 d,复杂度为 225。
S(x) 可以数论分块求得。
设 x=p1k1p2k2⋯pmkm,我们知道因数个数 σ0(x)=(k1+1)(k2+1)⋯(km+1)。
我们还知道 m! 中包含的 p 的幂次为 t=m/p+m/p/p+m/p/p/p+⋯。
设 ym!=p1k1p2k2⋯pmkm,则 ym! 中 p1 可取到的幂次为 0,1,2,⋯,k1。p2 也类似。则 d∣y,y∣m!∑σ0(ym!) 可以用类似生成函数的方式表示成 (1+2+⋯+(k1+1))⋅(1+2+⋯+(k2+1))⋯(1+2+⋯+(km+1))。
对于每个 d 的质因子 p,y 为 d 的倍数,所以 y 中 p 的幂次至少与 d 中相同,则 ym! 包含的 p 的幂次范围为 [0,t−1],所以 p 产生的贡献为 (1+2+⋯+t)。
而对于不是 d 的质因子的质数 p,ym! 包含的 p 的幂次范围为 [0,t],所以 p 产生的贡献为 (1+2+⋯+t+t+1)。
枚举每一个质数,加入或不加入 d,并据此及时计算 σ0(ym!),在最后一层计算 μ(d)S(dn)∑d∣y,y∣m!σ0(ym!)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <bits/stdc++.h> #define debug(x) cout << #x << ":\t" << (x) << endl; using namespace std; #define ll long long #define ull unsigned ll const int N = 2e6 + 10; const int INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; const ll mod = 998244353; int vis[N], prime[N], tot, cnt[N]; void sieve(int n) { for (int i = 2; i <= n; i++) { if (!vis[i]) { prime[++tot] = i; int x = n; while (x) { cnt[tot] += x / i; x /= i; } } int d; for (int j = 1; j <= tot && (d = i * prime[j]) <= n; j++) { vis[d] = 1; if (i%prime[j] == 0)break; } } } ll n; int m; ll S(ll n) { ll res = 0; for (ll l = 1, r; l <= n; l = r + 1) { r = n / (n / l); res = (res + (r - l + 1)*(n / l) % mod) % mod; } return res; } ll ans; void dfs(int dep, ll d, int mu, ll the) { if (dep == tot + 1) { ans = (ans + mu * S(n / d)*the%mod + mod) % mod; return; } dfs(dep + 1, d, mu, (2 + cnt[dep])*(1 + cnt[dep]) / 2 % mod*the%mod); if (d*prime[dep] <= n) { dfs(dep + 1, d*prime[dep], -mu, (1 + cnt[dep])*cnt[dep] / 2 % mod*the%mod); } } int main() { scanf("%lld%d", &n, &m); sieve(m); dfs(1, 1, 1, 1); printf("%lld\n", ans); return 0; }
|