三道排列组合好题
或许是人品不错,最近两天接连刷到三道排列组合相关的题。在此作简单整理。
AtCoder ABC226 F – Score of Permutations
题意参见原题面。
很自然地想到,对于一个排列,有条的连边。因为排列中正整数恰好出现仅有一次,所以等价于每个点有且仅有一条出边、一条入边的图。容易发现这个图由若干个有向环构成(包括自环——即的情况)。例如排列代表由和两个环组成的图。
根据题意,一个排列的分数,就是其构成的图中各个环长度的最小公倍数。容易发现,一条长度确定的环,它对分数的贡献与其包含的点的编号等无关。所以我们考虑怎么分配这些环的长度。易得所有这些环的长度之和为,则这个问题可以转化成有多少种互不相同的方法,可以将自然数分成若干个自然数之和。
假定我们已经求出一种分配方法,,其中表示某个自然数,表示其出现次数。则由排列组合的知识可得,一共有种将点分配进各个环的方式。又因为我们重复统计了相同的长度为的环(例如,对于,分配个点,的分配和的分配被上面的式子算成了两种方式,但它们对应到排列中完全相同),所以答案还要除以。又发现,在每一个长度为的环内部,假设我们钦定环的一个起点,那么剩下的点将有种环内排列方式,也就是本质不同的环数。所以方案数还要再乘上。设上面式子的计算结果为,我们求出,则这种分配方法将对答案产生的贡献。
例如我们考虑时的一种拆分,则有种分配方法,其中对于长为的环,重复计算了次,对于长度为的环,重复计算了次;各个环内部的不同顺序由乘法原理可得有种,故,每种排列的分数为,故将对答案产生的贡献。
我们利用剪枝优化的搜索求出的所有不同拆分方法。实际计算得到,共有约种拆分方式,再加上快速幂的时间,可以通过本题。
另有一种非常高效的DP解法。在此不做讨论。
- 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> using namespace std; const int N = 64, P = 998244353; int n, k, g[N], ans, fact[N], f; int fast_pow (int x, int y) { int res = 1 % P; for (; y; y >>= 1) { if ((y & 1)) res = 1ll * res * x % P; x = 1ll * x * x % P; } return res; } int gcd (int a, int b) { return a % b ? gcd (b, a % b) : b; } int C (int n, int m) { return 1ll * fact[n] * fast_pow (1ll * fact[m] * fact[n - m] % P, P - 2) % P; } void terminate (int len) { int q = n, res = 1, freq, now, lcm; for (int i = 1; i <= len; ++i) res = 1ll * res * C (q, g[i]) % P * fact[g[i] - 1] % P, q -= g[i]; now = g[1], freq = 1, g[len + 1] = -1, lcm = g[1]; for (int i = 2; i <= len + 1; ++i) if (now != g[i]) { res = 1ll * res * fast_pow (fact[freq], P - 2) % P; freq = 1, now = g[i]; lcm = lcm * g[i] / gcd (lcm, g[i]); } else ++freq; ans = (1ll * ans + 1ll * fast_pow (lcm, k) * res % P) % P; f += res; } void calc (int sum, int ima, int pos) { if (sum == n) { terminate (pos); return; } for (int i = ima; i <= min (n, n - sum); ++i) { g[pos + 1] = i; calc (sum + i, i, pos + 1); } } int main () { /* ABC226 F - Score of Permutations 吴秋实 */ fact[0] = 1; for (int i = 1; i <= 52; ++i) fact[i] = 1ll * fact[i - 1] * i % P; scanf ("%d%d", &n, &k); calc (0, 1, 0); printf ("%d", ans); return 0; }
NOIP模拟 20211112 T1 – 谜之阶乘
给定正整数,你需要求出所有使得成立的。。
天河一号都受不了暴力枚举的复杂度。所以我们考虑聪明一点的方法。
我们发现,,所以无论怎样,若是,那么必然有。现在,我们钦定一个,则考虑,它显然具有关于的单调性。所以我们枚举,然后二分的值,最后检查计算出的答案是否和相等即可。
同时需要注意,由于增长极快,二分的上限不能随意设置。详见代码。
时间复杂度,为测试数据组数。非常优秀,可以把std吊起来打。
- 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
#include <bits/stdc++.h> using namespace std; int T, cnt; long long n, x, l, mid, r; long long lmt[24] = {0, (long long)1e18 + 10, (long long)1e9 + 10, (long long)1e6 + 10, 31650, 4000, 1010, 380, 190, 110, 70, 50, 40, 32, 27, 24, 22, 21, 20, 20}; struct ans { long long a, b; friend bool operator < (ans x, ans y) { return x.a < y.a; } } g[24]; long long calc (long long x, int k) { long long res = 1; for (long long i = x; i > x - k; --i) res *= i; return res; } void bi_search (int k) { l = k + 1, r = lmt[k]; while (l < r) { mid = (l + r + 1) >> 1; if (calc (mid, k) > n) r = mid - 1; else l = mid; } if (calc (l, k) == n) g[++cnt].a = l, g[cnt].b = l - k; } int main () { /* MOCK NOIP 20211112 吴秋实 */ scanf ("%d", &T); while (T--) { scanf ("%lld", &n); if (n == 1) { puts ("-1"); continue; } cnt = 0; for (int i = 1; i <= 19; ++i) bi_search (i); printf ("%d\n", cnt); sort (g + 1, g + cnt + 1); for (int i = 1; i <= cnt; ++i) printf ("%lld %lld\n", g[i].a, g[i].b); } return 0; }
AtCoder ABC 227 G – Divisors of Binomial Coefficient
计算二项式系数的因数个数。。答案对取余。
这个式子可以化成,也就是。
我们当然不可能真的把这个数的因数个数地求出来再加减,但由算术基本定理可得,对于一个确定的质因子,其在中的个数就是中每个数包含的的个数的和。更进一步,到中,的倍数,也就是至少包含一个质因子的数的个数显然有个。同理,包含至少两个该质因子,即的倍数的数有个。不过,对于每个这样的数,其中有一个质因子已经在中统计过了,所以我们累加上即可。
又因为除了质数外,中每一个数的质因子不可能超过,所以我们使用线性筛求出到的质数,对于一个质数,其在中的质因子个数就是。再考虑,同理可得包含的质因子的个数是,所以将的质因子计数减去上式即可。
同时,为了筛出中剩余的大于的质因数,我们记录该区间中每一个数当前剩余的因数积,对于每一个,我们将的因数积不断除以该质因数,直至无法整除为止。最后检查,发现其不是,说明其包含且恰好包含一个某个大于质因数。可以证明,最后筛选出的这些质因数在整个式子中仅出现一次。
由算术基本定理推得,最终答案就是。
由于每个质因数都会遍历一遍值域然后作除法,故复杂度是。
TMD线性筛写错了——暴力手搓数据调了2个多小时……
- 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
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10, P = 998244353; int p[N], fac[N], pcnt; long long cnt[N], haji; long long fuck[N]; long long n, k, g, q, ans; int main () { /* */ scanf ("%lld%lld", &n, &k); if (k > n - k) k = n - k; g = max ((long long)sqrt (n), k); // 计算n! / (n - k)!相关的质因子。 for (long long i = n - k + 1; i <= n; ++i) fuck[i - (n - k)] = i; for (int i = 2; i <= g; ++i) { if (!fac[i]) { fac[i] = p[++pcnt] = i; q = i; while (q <= n) { cnt[pcnt] += n / q - (n - k) / q - k / q; q = q * i; } haji = (n - k + 1) % i ? i - ((n - k + 1) % i) + n - k + 1 : n - k + 1; for (; haji <= n; haji += i) while (fuck[haji - (n - k)] % i == 0) fuck[haji - (n - k)] /= i; } for (int j = 1; j <= pcnt; ++j) { if (i * p[j] > g || fac[i] < p[j]) break; fac[i * p[j]] = p[j]; } } for (long long i = n - k + 1; i <= n; ++i) if (fuck[i - (n - k)] != 1) cnt[++pcnt] = 1, p[pcnt] = fuck[i - (n - k)]; ans = cnt[1] + 1; for (int i = 2; i <= pcnt; ++i) ans = 1ll * ans * (cnt[i] + 1) % P; printf ("%d\n", ans); return 0; }