被完虐... QwQ
#T1
#Problem
给定 ,求:
#Solution
#60pts
考场上最初以为是莫比乌斯反演的题,先简单推一推:
推到这里,我已经做不下去了,这样的时空复杂度可以做到 ,可得 ,注意到,这里无法继续进行的原因是幂没有合适的处理方法。
#100pts
接着上面的错误思路返回原点,突然想起欧拉函数具有以下性质:
于是原式化为
对于后面的和式,不难发现 是积性函数 , 是完全积性函数 ,于是我们便可以用线性筛筛出这两个函数,其中在 为质数时, 需要使用一次快速幂,而小于等于 的质数个数 约为 ,于是线性筛的整体时间复杂度约为 ,可以接受。
我们再线性推个逆元,直接求和即得答案。
#Code
#60pts
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 #include <bits/stdc++.h> #define ll long long #define int long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std;const int M = 10010 ;const int N = 10000010 ;const ll MOD = 1e9 + 7 ;const int INF = 0x3fffffff ;int n, p, nprm[N], prm[N], pcnt;ll mi[M][M], mu[N], sum[M][M], ans, sum1[N]; inline ll fpow (ll a, ll b) { ll res = 1 ; while (b) { if (b & 1 ) (res *= a) %= MOD; (a *= a) %= MOD; b >>= 1 ; } return res; } void euler (int x) { mu[1 ] = 1 ; for (int i = 2 ; i <= x; i ++) { if (!nprm[i]) prm[++ pcnt] = i, mu[i] = -1 ; for (int j = 1 ; j <= pcnt; j ++) { if (prm[j] * i > x) break ; nprm[prm[j] * i] = true ; if (i % prm[j]) mu[i * prm[j]] = -mu[i]; else break ; } } } signed main () { scanf ("%lld%lld" , &n, &p); euler (n); if (p == 1 ) { for (int i = 1 ; i <= n; ++ i) sum1[i] = (sum1[i - 1 ] + i) % MOD; for (int d = 1 ; d <= n; ++ d) { ans += mu[d] * sum1[n / d] % MOD; (ans += MOD) %= MOD; } } else { for (int i = 1 ; i <= n; ++ i) for (int j = 0 ; j <= p; ++ j) { mi[i][j] = fpow (i, j) % MOD; sum[i][j] = (sum[i - 1 ][j] + mi[i][j]) % MOD; } for (int i = 1 ; i <= p; ++ i) for (int d = 1 ; d <= n; ++ d) { ans += mu[d] * mi[d][i - 1 ] % MOD * sum[n / d][i] % MOD; (ans += MOD) %= MOD; } } printf ("%lld" , ans); return 0 ; }
#100pts
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 #include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std;const int M = 10010 ;const int N = 10000010 ;const ll MOD = 1e9 + 7 ;const int INF = 0x3fffffff ;ll n, p, mu[N], nprm[N], prm[N], pcnt; ll ans, phi[N], f[N], mi[N], inv[N]; inline ll fpow (ll a, int b) { ll res = 1 ; while (b) { if (b & 1 ) (res *= a) %= MOD; (a *= a) %= MOD; b >>= 1 ; } return res; } void euler (int x) { phi[1 ] = 1 ; f[1 ] = 1 ; for (int i = 2 ; i <= x; i ++) { if (!nprm[i]) { prm[++ pcnt] = i, mi[i] = fpow (i, p); phi[i] = i - 1 , f[i] = phi[i] * mi[i] % MOD; } for (int j = 1 ; j <= pcnt; j ++) { if (prm[j] * i > x) break ; nprm[prm[j] * i] = true ; if (i % prm[j]) { f[i * prm[j]] = f[i] * f[prm[j]] % MOD; phi[i * prm[j]] = phi[i] * (prm[j] - 1 ) % MOD; } else { phi[i * prm[j]] = phi[i] * prm[j] % MOD; f[i * prm[j]] = f[i] * prm[j] % MOD * mi[prm[j]] % MOD; break ; } } } } int main () { scanf ("%lld%lld" , &n, &p); euler (n); inv[0 ] = inv[1 ] = 1 ; for (int i = 2 ; i <= n; ++ i) inv[i] = ((MOD - MOD / i) * inv[MOD % i]) % MOD; for (int i = 2 ; i <= n; ++ i) (ans += inv[i - 1 ] * (f[i] - phi[i]) % MOD + MOD) %= MOD; printf ("%lld" , (ans + p % MOD + MOD) % MOD); return 0 ; }
#T2
巨大诈骗题,赛时想麻烦了(
#Problem
一个含有 个点的大根堆是一个二叉树,左右儿子有区别,每个节点上有一个 的值,任意两个节点上的值不同,且父亲节点的权值大于子节点的权值。
现在给定一个集合 ,要求 ,值 对应的节点是叶结点。求有多少个满足要求的大根堆,答案 。
#Solution
首先明确概念:堆不一定是完全二叉树!
“左偏堆是不是堆?左偏堆是完全二叉树吗?” ——wzy
因为是一个大根堆,于是插入顺序可以直接确定为由大到小插入,我们设当前可选位置个数为 ,不难发现,对于一个必须为叶子的节点,不论放到哪里,下一个点的可选位置一定减少一,如果不是,那么自己占了一个位置,又提供了两个可选位置,于是下一个点的可选位置一定增加一,将每一步的 相乘即得答案。
#Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std;const int N = 1000010 ;const ll MOD = 1e9 + 7 ;const int INF = 0x3fffffff ;int n, p, vis[N];ll s = 1 , ans = 1 ; int main () { scanf ("%d%d" , &n, &p); for (int i = 1 ; i <= p; ++ i) { int x; scanf ("%d" , &x); vis[x] = 1 ; } for (int i = n; i >= 1 ; -- i) { (ans *= s) %= MOD; if (vis[i]) -- s; else ++ s; } printf ("%lld" , ans); return 0 ; }
#T3
#Problem
初始有一张含有 个点的有向图,你可以不断向其中加入至多 条边,不允许加入重边或者自环。
定义一个长度为 的序列 为一个 SCC 序列,当且仅当存在一种加边方案,使得加入 条边后图中恰好存在 个强连通分量(两个点在一个强连通分量中当且仅当他们能够相互到达)。
给定 ,你需要求出当序列长度分别为 时,有多少种 SCC 序列,由于答案可能很大,需要对于一个数字取模。为了防止打表,模数是输入的。
#Solution
用的 zyc 大佬的思路,这里说的详细一点。
我们考虑这个序列字典序最大是多少,假设 ,那么这个序列是:
记该序列为 我们考虑这个序列是怎么形成的,一开始有 个空点,然后相邻连边,就可以使 scc 个数不变。加的不能加为止,不难发现,每多连一条边,就可以有 条边使得 scc 个数为 不变。这个自己画一下图就能论证。
接下来我们考虑如果已知一个前缀 scc 序列,如何看下一个值最小能填多少?我们不难发现,这个数是 ,其中 是序列长度, 是序列中不同的数的个数。这个很好论证,用构造的方法就可以证明。
至于上界,这个就是上一位和这一位的 值做 就可以。
上下界固定后,暴力就好想了,DP 也很自然。
设 表示序列长度为 ,最后一个数为 ,有 个不同的数的方案数。
我们考虑有哪些状态可以转移到 。我们考虑如果这个序列中 不止一个,那么就可以从 中转移。
否则,我们枚举第 个填了什么,然后转移,不难发现这个东西是 其中 ,因为不合法的状态我们已经设置为 ,所以可以放心转移。用前缀和优化,可以做到 O(Tn^4)。
空间简单卡一下可以通过,保险起见,这里采用滚动数组优化。
#Code
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 #include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std;const int N = 101 ;const int INF = 0x3fffffff ;int f[2 ][N][N], pre[N][N], MOD, mxl[N * N];inline void prework (int n) { for (int i = 1 ; i <= n * (n - 1 ) / 2 ; ++ i) mxl[i] = n; for (int i = n - 1 ; i; -- i) for (int j = (n - i) * (n - i - 1 ) / 2 + 1 ; j <= (n - i) * (n - i - 1 ) / 2 + n - i; ++ j) mxl[n * (n - 1 ) / 2 + j] = i; } inline int red (int x) {return x >= MOD ? x - MOD : x;}inline void solve (int n) { mset (f, 0 ); f[1 ][n][1 ] = 1 ; printf ("1 " ); for (int i = 2 ; i <= n * (n - 1 ); ++ i) { mset (f[i & 1 ], 0 ); int ans = 0 ; for (int j = 1 ; j <= n && j <= i; ++ j) { pre[1 ][j] = f[!(i & 1 )][1 ][j]; for (int k = 2 ; k <= mxl[i - 1 ]; ++ k) pre[k][j] = red (pre[k - 1 ][j] + f[!(i & 1 )][k][j]); } for (int j = 1 ; j <= mxl[i]; ++ j) for (int k = 1 ; k <= n && k <= i && k <= (n - j + 1 ); ++ k) { int l = max (j, n - i + k - 1 ); if (l > j) continue ; f[i & 1 ][j][k] = (pre[mxl[i - 1 ]][k - 1 ] - pre[l + (l == j) - 1 ][k - 1 ] + MOD) % MOD; if (l == j) f[i & 1 ][j][k] = red (f[i & 1 ][j][k] + f[!(i & 1 )][j][k]); ans = red (ans + f[i & 1 ][j][k]); } printf ("%d " , ans); } printf ("\n" ); } int main () { int T, n; scanf ("%d" , &T); while (T --) { scanf ("%d%d" , &n, &MOD); prework (n); solve (n); } return 0 ; }
#T4
#Problem
有一个长度为 的数列 ,Alice 和 Bob 手上分别有一个初始为 的数字(分别记为 )。Alice 和 Bob 轮流做如下操作:
从序列开头或者结尾取出一个数 ,让自己手上的数字异或上 ,并把 从序列中删除。
Alice 先手,最后谁手上的数字大谁就胜了。如果 Alice,Bob 均使用最优策略,那么谁能取胜?(或者平局,即两个人手上的数字一样大)
#Solution
首先不难发现,当 的异或和为 时一定为平局,否则 在二进制下最高的非零位 必然是决胜的关键,于是我们便可以将原本的 按照第 位是否为 转变为 01 序列,下面的操作都是在 01 序列上进行的讨论。
分为两种情况:长度为奇数和长度为偶数。
先来看第一种:长度为偶数。我们将 01 序列分为奇数位 和偶数位 ,此时 a_i 的异或和不为 ,所以必然要么是奇数位上有奇数个 ,要么是偶数位上有奇数个 ,于是先手可以直接控制后手所选的 的奇偶性,故在此种情况下先手必胜。
再来看长度为奇数的情况:
假如两端都是 ,那么无论先手选哪个都会使当前后手变为长度为偶数的先手状态,于是此时先手必败;
假如一端为 ,那么先手必然要选作为 的一端,否则必败,不是最优策略;之后先手需要跟随后手的选择,保证两者的选择完全一致,否则必败我们来简单讨论一下原因:
假如在这一步之前,所有的先手的每一步都跟随着后手选,所以此时先后手选的 的数量的奇偶性一定不同,在这一步时,后手选了 ,先手选了 ,那么此时先后手选的 的数量的奇偶性变为相同,于是一共选了偶数个 ,还剩奇数个 ,此时一共选了奇数个数字,于是还剩偶数个数字,此时后手作为当前局面的先手进入必胜状态;于是这样选先手必败。
在这一步时,后手选了 ,先手选了 ,与上面的证明同理,可得这样先手必败。
由于先手取走了一个 ,那么此时 的个数为偶数,由于每一步先手都跟着后手选,所以先后手必然平分偶数个 ,如果这个个数不能被 整除,也就意味着先手被分到了奇数个 ,加上了最初的那个 ,必败。
那么,怎样的序列才能满足先手能跟着后周的每一步选呢?删除掉左右两边相同的数后,剩下的应当满足相邻奇偶位(如 和 是,但 和 不是)上的数字相同,这里不证正确性,证法与上面相似。
#Code
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 #include <bits/stdc++.h> #define ll long long #define mset(l, x) memset(l, x, sizeof(l)) using namespace std;const int N = 200010 ;const int INF = 0x3fffffff ;int a[N], s, n, T, l, r, p, tot;inline bool check (int l, int r, int cnt) { while (l < r && a[l] == a[r]) ++ l, -- r; while (l < r && a[l] == a[l + 1 ]) l += 2 ; if (l < r) return 0 ; else return 1 ; } int main () { scanf ("%d" , &T); while (T --) { scanf ("%d" , &n); l = 1 , r = n; p = 30 , tot = 0 , s = 0 ; for (int i = 1 ; i <= n; ++ i) scanf ("%d" , &a[i]), s ^= a[i]; if (!s) {printf ("Draw\n" ); continue ;} if (!(n & 1 )) {printf ("Alice\n" ); continue ;} while (p && !((s >> p) & 1 )) -- p; for (int i = 1 ; i <= n; ++ i) a[i] = (a[i] >> p) & 1 , tot += a[i]; if (a[1 ] == 0 && a[n] == 0 ) {printf ("Bob\n" ); continue ;} -- tot; if (tot % 4 ) {printf ("Bob\n" ); continue ;} if ((a[1 ] && check (2 , n, tot)) || (a[n] && check (1 , n - 1 , tot))) printf ("Alice\n" ); else printf ("Bob\n" ); } return 0 ; }
期望得分: 实际得分: 没想到还骗过了一个点(
Gitalk 加载中 ...