2022CCPC黑龙江省赛题解ACEFGHIL
题目链接:
https://codeforces.com/gym/103688
代码链接:
https://hytidel.lanzoub.com/b031d28yh
密码:evfs
I. Equal Sum Arrays
题意
对 ∀ n ∈ N ∗ \forall n\in\mathbb{N}^* ∀n∈N∗,定义函数 f ( n ) f(n) f(n)为使得元素之和为 n n n的正整数序列的个数.如 f ( 3 ) = 4 f(3)=4 f(3)=4,满足的序列有 { 1 , 1 , 1 } , { 1 , 2 } , { 2 , 1 } , { 3 } \{1,1,1\},\{1,2\},\{2,1\},\{3\} {1,1,1},{1,2},{2,1},{3}.给定 k ( 1 ≤ k ≤ 20 ) k\ \ (1\leq k\leq 20) k (1≤k≤20),求 f ( k ) f(k) f(k).
思路
对 f ( n ) f(n) f(n),转化为在 n n n个小球排成一行的 ( n − 1 ) (n-1) (n−1)个空隙中插板,每个空隙可插可不插,显然 f ( n ) = 2 n − 1 f(n)=2^{n-1} f(n)=2n−1.
代码 -> 2022CCPC黑龙江省赛-I(思维)
int main() {
int n; cin >> n;
cout << (1 << n - 1);
}
F. 342 and Xiangqi
题意
如上图为中国象棋中一方的象所能到达的位置.
有 t ( 1 ≤ t ≤ 1 e 5 ) t\ \ (1\leq t\leq 1\mathrm{e}5) t (1≤t≤1e5)组测试数据.每组测试数据输入四个整数 a 1 , a 2 , b 1 , b 2 ( 1 ≤ a 1 , a 2 , b 1 , b 2 ≤ 7 , a 1 ≠ a 2 , b 1 ≠ b 2 ) a_1,a_2,b_1,b_2\ \ (1\leq a_1,a_2,b_1,b_2\leq 7,a_1\neq a_2,b_1\neq b_2) a1,a2,b1,b2 (1≤a1,a2,b1,b2≤7,a1=a2,b1=b2),分别表示初始时一方的象的位置和目标位置.求将两只象从初始位置移动到目标位置的最少步数,两只象视为相同.
思路
任一有交叉的路径(即移动过程中一只象可能移动到与另一只象相同的位置)可调整为不交叉的路径.
[证] 因两只象视为相同,则在路径即将发生交叉时,可视为要移动的象A的灵魂传给了在其下一步到达的位置上的象B,
随后象B代替A到达目标位置,此时路径即可不交叉.
路径不交叉,则两只象的移动可视为独立,建图后用Floyd求出任意两个象可到达的位置间的最短路即可.时间复杂度 O ( 7 3 ) O(7^3) O(73).
代码 -> 2022CCPC黑龙江省赛-F(思维+Floyd)
const int MAXN = 10;
int n = 7; // 节点数
int dis[MAXN][MAXN]; // dis[u][v]表示节点u到节点v的最短路
void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
void init() {
// 初始化
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) dis[i][j] = 0;
else dis[i][j] = INF;
}
}
// 建图
dis[1][2] = dis[1][3] = 1;
dis[2][1] = dis[2][4] = 1;
dis[3][1] = dis[3][4] = 1;
dis[4][2] = dis[4][3] = dis[4][5] = dis[4][6] = 1;
dis[5][4] = dis[5][7] = 1;
dis[6][4] = dis[6][7] = 1;
dis[7][5] = dis[7][6] = 1;
floyd(); // 预处理出任意两节点间的最短路
}
void solve() {
init();
CaseT{
int a1, a2, b1, b2; cin >> a1 >> a2 >> b1 >> b2;
int ans = dis[a1][b1] + dis[a2][b2];
ans = min(ans, dis[a1][b2] + dis[a2][b1]);
cout << ans << endl;
}
}
int main() {
solve();
}
H. Kanbun
题意
给定一个长度为 n ( 3 ≤ n ≤ 1 e 5 ) n\ \ (3\leq n\leq 1\mathrm{e}5) n (3≤n≤1e5)的只包含’(‘、’-‘、’)‘且符合括号匹配规则的字符串,下标从 1 1 1开始.按如下规则阅读该字符串,输出每次阅读的字符的下标:①从第一个字符开始阅读;②对第 i i i个字符,若它是’-‘或’)‘,则直接阅读,并跳到第 ( i + 1 ) (i+1) (i+1)个字符;③对第 i i i个字符,若它是’(‘,先找到与其匹配的’)',假设其下标为 j j j,先阅读第 ( i + 1 ) (i+1) (i+1)到第 j j j个字符,再阅读第 i i i个字符,最后跳到第 ( j + 1 ) (j+1) (j+1)个字符;④阅读到第 ( n + 1 ) (n+1) (n+1)个字符时终止.
思路I
实现函数dfs(l,r)表示阅读 s [ l ⋯ r ] s[l\cdots r] s[l⋯r].从左往右阅读,遇到’-‘或’)‘时直接阅读;遇到’('时先找到与其匹配的右括号,再递归到它们之间的区间即可.注意边界有 l > r l>r l>r时的"()“和 l = r l=r l=r”(-)"两种情况.
最坏的情况是字符串为 5 e 4 5\mathrm{e4} 5e4个左括号和 5 e 4 5\mathrm{e}4 5e4个右括号,若对每个左括号都暴力找到其对应的右括号,时间复杂度 1 + 2 + ⋯ + ( n − 1 ) = n ( n − 1 ) 2 1+2+\cdots+(n-1)=\dfrac{n(n-1)}{2} 1+2+⋯+(n−1)=2n(n−1),会TLE.考虑优化,注意可在DFS前先预处理出每个左括号匹配的右括号,该过程可通过栈实现.总时间复杂度 O ( n ) O(n) O(n).
代码I -> 2022CCPC黑龙江省赛-H(DFS)
const int MAXN = 1e5 + 5;
int n; // 长度
char s[MAXN];
int match[MAXN]; // match[l]=r表示下标为l的左括号匹配下标为r的右括号
void dfs(int l, int r) {
if (l > r) return; // ()的情况
if (l == r) { // (-)的情况
cout << l << ' ';
return;
}
for (int i = l; i <= r; i++) {
if (s[i] == '-' || s[i] == ')') cout << i << ' ';
else {
// 暴力求每个左括号匹配的右括号,TLE
//int sum = 1; // 左括号+1,右括号-1
//int j = i + 1;
//while (sum) {
// if (s[j] == '(') sum++;
// else if (s[j] == ')') {
// sum--;
// if (!sum) break;
// }
// j++;
//}
int j = match[i];
// cout << '(' << i + 1 << ',' << j - 1 << ") "; // 每次递归到的小区间
dfs(i + 1, j - 1);
cout << j << ' ';
cout << i << ' ';
i = j; // 因为还有i++
}
}
}
void solve() {
cin >> n >> s + 1;
// 预处理每个左括号对应的右括号
stack<int> stk;
for (int i = 1; i <= n; i++) {
if (s[i] == '(') stk.push(i); // 左括号入栈
else if (s[i] == ')') { // 找到一组匹配
match[stk.top()] = i;
stk.pop(); // 左括号出栈
}
}
dfs(1, n); // 从整个串开始搜
}
int main() {
solve();
}
思路II
观察样例知:该阅读顺序等价于遇到’-‘直接输出下标;遇到’(‘时待输出与其匹配的’)'的下标后再输出其下标.该过程可用栈实现,时间复杂度 O ( n ) O(n) O(n).
代码II -> 2022CCPC黑龙江省赛-H(思维)
const int MAXN = 1e5 + 5;
int n; // 长度
char s[MAXN];
void solve() {
cin >> n >> s + 1;
// 预处理每个左括号对应的右括号
stack<int> stk;
for (int i = 1; i <= n; i++) {
if (s[i] == '-') cout << i << ' ';
else if (s[i] == '(') stk.push(i); // 左括号入栈
else { // 找到一组匹配
cout << i << ' ' << stk.top() << ' ';
stk.pop(); // 左括号出栈
}
}
}
int main() {
solve();
}
A. Bookshelf Filling
题意
A型书高度为 a a a,宽度为 1 1 1;B型书高度为 b ( b > a ) b\ \ (b>a) b (b>a),宽度为 1 1 1.有一高度为 h ( h ≥ b ) h\ \ (h\geq b) h (h≥b)的书架, n n n本A型书和 m m m本B型书.现将书按高度非降序依次放在书架上,即B型书都在A型书的右边.现可从最右边选 k ( 0 ≤ k ≤ m − 1 ) k\ \ (0\leq k\leq m-1) k (0≤k≤m−1)本B型书横着放在竖着放的书的上方.求将所有书放好至少需占用书架的宽度.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据输入四个整数 a , b , n , m , h ( 1 ≤ a < b ≤ h ≤ 1 e 6 , 1 ≤ n , m ≤ 1 e 9 ) a,b,n,m,h\ \ (1\leq a<b\leq h\leq 1\mathrm{e}6,1\leq n,m\leq 1\mathrm{e}9) a,b,n,m,h (1≤a<b≤h≤1e6,1≤n,m≤1e9).
思路
设竖着放的B型书剩下 x x x本,则可放剩下的 ( m − x ) (m-x) (m−x)本B型书的区域为上图的紫色和粉色区域,显然它们最多能放的B型书共 [ ( b − a ) ⋅ ⌊ n b ⌋ + ( h − b ) ⋅ ⌊ n + x b ⌋ ] \left[(b-a)\cdot\left\lfloor\dfrac{n}{b}\right\rfloor+(h-b)\cdot \left\lfloor\dfrac{n+x}{b}\right\rfloor\right] [(b−a)⋅⌊bn⌋+(h−b)⋅⌊bn+x⌋]本,注意此处可能爆int.
因为竖着放的B型书越少,则越可能放不下,即具有二段性.二分 x x x,判断能放得下的B型书数是否 ≥ m \geq m ≥m即可.注意至多选 ( m − 1 ) (m-1) (m−1)本B型书,故二分的左边界为 1 1 1.
代码 -> 2022CCPC黑龙江省赛-A(二分)
const int MAXN = 1e5 + 5;
ll a, b, n, m, h;
bool check(int x) {
return (b - a) * (n / b) + (h - b) * ((n + x) / b) + x >= m;
}
void solve() {
cin >> a >> b >> n >> m >> h;
int l = 1, r = m; // 注意l最小为1
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << n + l << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
L. Let’s Swap
题意
现有一个对字符串 s s s的操作:先选择 s s s的一个长度为 l ( 1 ≤ l ≤ ∣ s ∣ ) l\ \ (1\leq l\leq |s|) l (1≤l≤∣s∣)的前缀,则 s s s可表示为 A B AB AB,其中 ∣ A ∣ = l , B |A|=l,B ∣A∣=l,B可为空串.交换两部分,得到字符串 B A BA BA,再反转整个串,得到 ( B A ) r (BA)^r (BA)r.现对前缀长度的选择有限制,只能选择长度 l 1 l_1 l1或 l 2 l_2 l2.
有 t ( 1 ≤ t ≤ 5 e 5 ) t\ \ (1\leq t\leq 5\mathrm{e}5) t (1≤t≤5e5)组测试数据.每次测试数据第一行输入字符串 s s s,第二行输入字符串 t t t,第三行输入两个整数 l 1 , l 2 ( 1 ≤ l 1 , l 2 ≤ ∣ s ∣ = ∣ t ∣ , l 1 ≠ l 2 ) l_1,l_2\ \ (1\leq l_1,l_2\leq |s|=|t|,l_1\neq l_2) l1,l2 (1≤l1,l2≤∣s∣=∣t∣,l1=l2).数据保证所有测试数据的 ∣ s ∣ |s| ∣s∣之和不超过 5 e 5 5\mathrm{e}5 5e5.
对每组测试数据,若能通过若干次(可能为零次)操作将 s s s转化为 t t t,则输出"yes";否则输出"no".
思路
设取长度为 l 1 l_1 l1、 l 2 l_2 l2的前缀的操作分别为 A A A和 B B B.注意到连续两个同种操作会相互抵消,则操作方式只能为 A B A B ⋯ ABAB\cdots ABAB⋯或 B A B A ⋯ BABA\cdots BABA⋯,而 ( A B ) − 1 = B A (AB)^{-1}=BA (AB)−1=BA,则操作方式有 ( A B ) n (AB)^n (AB)n和 A ( A B ) n A(AB)^n A(AB)n两种,其中 n n n可为负数.
先对串 s s s做一次操作 A A A得到串 t m p s tmps tmps.预处理出串 s s s、 t t t、 t m p s tmps tmps的哈希值 h a 1 [ ] ha1[] ha1[]、 h a 2 [ ] ha2[] ha2[]、 h a 3 [ ] ha3[] ha3[].每次对串 s s s和 t m p s tmps tmps做 n n n次操作 A B AB AB,每次考察得到的串的哈希值是否等于 h a 2 [ n ] ha2[n] ha2[n].
本题至此已可以AC,但事实上还可优化.注意到一次操作 A B AB AB、 B A BA BA在原串上等价于循环左移、右移 ∣ l 1 − l 2 ∣ |l_1-l_2| ∣l1−l2∣个单位,则合法的循环位移长度为 k ∣ l 1 − l 2 ∣ m o d n k|l_1-l_2|\ \mathrm{mod}\ n k∣l1−l2∣ mod n.取 d = gcd ( ∣ l 1 − l 2 ∣ , n ) d=\gcd(|l_1-l_2|,n) d=gcd(∣l1−l2∣,n),则两种操作每次平移 d d d个单位,至多平移 n d \dfrac{n}{d} dn次即可.
代码 -> 2022CCPC黑龙江省赛-L(字符串哈希) By : Sakurasss
const int MAXN = 5e5 + 5;
const int P = 13331;
string s, t, tmps; // 原串、目标串、原串做一次A操作得到的串
int l1, l2; // 可选前缀长度
int n; // 长度
ull ha1[MAXN], ha2[MAXN], ha3[MAXN]; // s、t、tmps的哈希值
ull Ppow[MAXN]; // P的乘方
ull get_hash(int op, int l, int r) { // 求s、t、tmps子串[l,r]的哈希
if (op == 1) return ha1[r] - ha1[l - 1] * Ppow[r - l + 1];
else if (op == 2) return ha2[r] - ha2[l - 1] * Ppow[r - l + 1];
else return ha3[r] - ha3[l - 1] * Ppow[r - l + 1];
}
void init() { // // 预处理出三个字符串的哈希值
Ppow[0] = 1;
for (int i = 1; i <= n; i++) {
ha1[i] = ha1[i - 1] * P + s[i];
ha2[i] = ha2[i - 1] * P + t[i];
ha3[i] = ha3[i - 1] * P + tmps[i];
Ppow[i] = Ppow[i - 1] * P;
}
}
void solve() {
cin >> s >> t >> l1 >> l2;
// 对s串做一次A操作
tmps = s;
reverse(tmps.begin(), tmps.begin() + l1), reverse(tmps.begin() + l1, tmps.end());
n = s.length();
s = " " + s, t = " " + t, tmps = " " + tmps; // 下标从1开始
init();
// 枚举做变换AB得到的串
int begin = 0; // 选择的前缀的长度
int d = gcd(abs(l1 - l2), n);
int t = n;
while (true) {
ull tmp1 = ha1[n - begin] + get_hash(1, n - begin + 1, n) * Ppow[n - begin];
ull tmp3 = ha3[n - begin] + get_hash(3, n - begin + 1, n) * Ppow[n - begin];
if (tmp1 == ha2[n] || tmp3 == ha2[n]) {
cout << "yes" << endl;
return;
}
begin += d;
if (begin > n) break;
}
cout << "no" << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
G. Chevonne’s Necklace
题意
n n n颗珍珠串成一串项链,珍珠顺时针编号 1 ∼ n 1\sim n 1∼n.每颗珍珠上有一个非负整数 c i c_i ci,每次操作可选择一颗满足 c i ≥ 1 c_i\geq 1 ci≥1的珍珠 i i i,移除从它顺时针开始数的 c i c_i ci颗珍珠.若剩下的珍珠数不足 c i c_i ci,则不能选择珍珠 i i i.操作后剩下的珍珠重新串成项链.求最多能移除多少颗珍珠及其方案数.用每次操作选择的珍珠的编号构成的集合来描述一个方案,两个方案不同当且仅当它们对应的集合不同.
第一行输入整数 n ( 1 ≤ n ≤ 2000 ) n\ \ (1\leq n\leq 2000) n (1≤n≤2000).第二行输入 n n n个整数 c 1 , ⋯ , c n ( 0 ≤ c i ≤ 2000 ) c_1,\cdots,c_n\ \ (0\leq c_i\leq 2000) c1,⋯,cn (0≤ci≤2000).
输出两个整数,第一个表示最多能移除多少颗珍珠,第二个表示其方案数,答案对 998244353 998244353 998244353取模.
思路
将珍珠视为物品,以 n n n为背包容量、 c i c_i ci为体积、 0 0 0为价值做背包,则方案数即背包的方案数.
[析] 可疑点:删除一个选中的珍珠时顺带将下一步要选择的珍珠也移除了,导致无法构造出背包的解.
[证] 只需对背包选出的方案构造出原题的一组解.设背包选中的珍珠下标为 p 1 , ⋯ , p k p_1,\cdots,p_k p1,⋯,pk,则 ∑ i = 1 k c p i ≤ n \displaystyle \sum_{i=1}^k c_{p_i}\leq n i=1∑kcpi≤n.
d i s ( p i ) dis(p_i) dis(pi)表示珍珠 p i p_i pi与顺时针方向的下一个选中的珍珠(即珍珠 p ( i m o d n ) + 1 p_{(i\ \mathrm{mod}\ n)+1} p(i mod n)+1)间的距离,
则只有 c p i ≤ d i s ( p i ) c_{p_i}\leq dis(p_i) cpi≤dis(pi)时才能删去珍珠 p i p_i pi.
下证每次操作时至少 ∃ \exists ∃一个珍珠 s . t . c p i ≤ d i s ( p i ) \ s.t.\ c_{p_i}\leq dis(p_i) s.t. cpi≤dis(pi).若不然,设所有珍珠都满足 c p i > d i s ( p i ) c_{p_i}>dis(p_i) cpi>dis(pi),
则 ∑ i = 1 k c p i > ∑ i = 1 k d i s ( p i ) = n \displaystyle\sum_{i=1}^k c_{p_i}>\sum_{i=1}^k dis(p_i)=n i=1∑kcpi>i=1∑kdis(pi)=n,矛盾.
故每次操作时都选中一个满足 c p i ≤ d i s ( p i ) c_{p_i}\leq dis(p_i) cpi≤dis(pi)的珍珠即可,而方案用集合描述,故移除顺序不影响方案数.
显然最终选中的珍珠是从珍珠 1 1 1开始的连续编号.
代码 -> 2022CCPC黑龙江省赛-G(背包)
const int MAXN = 2005;
const int MOD = 998244353;
int n;
int c[MAXN];
bool removed[MAXN]; // 记录每颗珍珠是否被移除
int dp[MAXN]; // dp[i]表示移除i颗珍珠的方案数
void solve() {
cin >> n;
for (int i = 0; i < n; i++) cin >> c[i];
removed[0] = true;
dp[0] = 1;
for (int i = 0; i < n; i++) { // 枚举物品
if (c[i]) { // c[i]≥1才能选中
for (int j = n; j >= c[i]; j--) { // 枚举体积
removed[j] |= removed[j - c[i]]; // 更新珍珠状态
dp[j] = ((ll)dp[j] + dp[j - c[i]]) % MOD;
}
}
}
int ans = n;
while (!removed[ans]) ans--;
cout << ans << ' ' << dp[ans];
}
int main() {
solve();
}
C. Tree Division
题意
给定一棵包含编号 1 ∼ n 1\sim n 1∼n的 n n n个节点的树,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个节点有权值 a i a_i ai.判断节点 1 1 1是否有效,若有效则输出"YES",否则输出"NO".定义节点 t t t有效,如果可将 n n n个节点划分为两个集合 A A A、 B B B,使得:①对 ∀ u , v ∈ A ( u ≠ v ) \forall u,v\in A\ \ (u\neq v) ∀u,v∈A (u=v),若 u u u是从节点 t t t到节点 v v v的路径,则 a u < a v a_u<a_v au<av;②对 ∀ u , v ∈ B ( u ≠ v ) \forall u,v\in B\ \ (u\neq v) ∀u,v∈B (u=v),若 u u u是从节点 t t t到节点 v v v的路径,则 a u > a v a_u>a_v au>av.
第一行输入一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5).第二行输入 n n n个整数 a i ( 1 ≤ a i ≤ n ) a_i\ \ (1\leq a_i\leq n) ai (1≤ai≤n).接下来 ( n − 1 ) (n-1) (n−1)行每行输入两个整数 x , y ( 1 ≤ x , y ≤ n ) x,y\ \ (1\leq x,y\leq n) x,y (1≤x,y≤n),表示节点 x x x与节点 y y y间存在边.
思路 By : watyrtle
注意到以节点 1 1 1为根节点时,节点 1 1 1有效的条件为:①对 ∀ u , v ∈ A ( u ≠ v ) \forall u,v\in A\ \ (u\neq v) ∀u,v∈A (u=v),若节点 v v v是以节点 u u u为根节点的子树中的节点,则 a u < a v a_u<a_v au<av,递推得 a v a_v av是根节点到节点 v v v的路径上权值最大的点,且之后放入集合 A A A中的节点的权值 > a v >a_v >av;②对 ∀ u , v ∈ B ( u ≠ v ) \forall u,v\in B\ \ (u\neq v) ∀u,v∈B (u=v),若节点 v v v是以节点 u u u为根节点的子树中的节点,则 a u > a v a_u>a_v au>av,递推得 a v a_v av是根节点到节点 v v v的路径上权值最小的点,且之后放入集合 B B B中的节点的权值 < a v <a_v <av.
贪心策略:将节点放入集合 A A A时,保证集合 B B B中的节点的最小值尽量大;将节点放入集合 B B B中时,保证集合 A A A中的节点的最大值尽量小. d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示将节点 u u u放入集合 A A A中时集合 B B B中的节点权值的最小值的最大值, d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示将节点 u u u放入集合 B B B中时集合 A A A中的节点权值的最大值的最小值.初始条件 d p [ 1 ] [ 0 ] = − I N F , d p [ 1 ] [ 1 ] = I N F dp[1][0]=-INF,dp[1][1]=INF dp[1][0]=−INF,dp[1][1]=INF,都表示不合法的状态.
设节点 u u u的一个子节点为 v v v.对 d p [ u ] [ 0 ] dp[u][0] dp[u][0],若 v v v能放入集合 A A A,由两种情况:① a v a_v av严格大于根节点到节点 v v v的路径上权值最大的节点(节点 u u u),即 a v > a u a_v>a_u av>au;②将 v v v放入集合 B B B中时集合 A A A中的节点权值的最大值的最小值(即 d p [ v ] [ 1 ] dp[v][1] dp[v][1])严格大于 a u a_u au,即 a u < d p [ v ] [ 1 ] a_u<dp[v][1] au<dp[v][1].同理可得 d p [ u ] [ 1 ] dp[u][1] dp[u][1]的状态转移.
状态转移方程: { d p [ u ] [ 0 ] = max e d g e < u , v > { min { a u < a v ? d p [ v ] [ 0 ] : I N F , a u < d p [ v ] [ 1 ] ? a v : I N F } } d p [ u ] [ 1 ] = min e d g e < u , v > { max { a u > a v ? d p [ v ] [ 1 ] : − I N F , a u > d p [ v ] [ 0 ] ? a v : − I N F } } \begin{cases}\displaystyle dp[u][0]=\max_{edge<u,v>}\{\min\{a_u<a_v\ ?\ dp[v][0]\ :\ INF,a_u<dp[v][1]\ ?\ a_v\ :\ INF\}\} \\ \displaystyle dp[u][1]=\min_{edge<u,v>}\{\max\{a_u>a_v\ ?\ dp[v][1]\ :\ -INF,a_u>dp[v][0]\ ?\ a_v\ :\ -INF\}\}\end{cases} ⎩ ⎨ ⎧dp[u][0]=edge<u,v>max{min{au<av ? dp[v][0] : INF,au<dp[v][1] ? av : INF}}dp[u][1]=edge<u,v>min{max{au>av ? dp[v][1] : −INF,au>dp[v][0] ? av : −INF}}.
若更新后 d p [ 1 ] [ 0 ] = I N F dp[1][0]=INF dp[1][0]=INF且 d p [ 1 ] [ 1 ] = − I N F dp[1][1]=-INF dp[1][1]=−INF,则节点 1 1 1无效,否则有效.
代码 -> 2022CCPC黑龙江省赛-C(贪心+树形DP)
const int MAXN = 1e5 + 5;
int n;
int a[MAXN];
vi edges[MAXN];
vi son[MAXN]; // son[u]表示节点u的儿子节点
// dp[u][0]表示将节点u放入集合A中时集合B中的节点权值的最小值的最大值
int dp[MAXN][2]; // dp[u][1]表示将节点u放入集合B中时集合A中的节点权值的最大值的最小值
void dfs1(int u, int fa) { // 预处理出以节点1为根节点时每个节点的儿子节点:当前节点、前驱节点
for (auto v : edges[u]) {
if (v == fa) continue;
son[u].push_back(v);
dfs1(v, u);
}
}
void dfs2(int u) { // 树形DP
for (auto v : son[u]) {
dfs2(v); // 递归求以v为根节点的子树的信息
dp[u][0] = max(dp[u][0], min(a[u] < a[v] ? dp[v][0] : INF, a[u] < dp[v][1] ? a[v] : INF));
dp[u][1] = min(dp[u][1], max(a[u] > a[v] ? dp[v][1] : -INF, a[u] > dp[v][0] ? a[v] : -INF));
}
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
dp[i][0] = -INF, dp[i][1] = INF; // 初始化为不合法状态
}
for (int i = 0; i < n - 1; i++) {
int x, y; cin >> x >> y;
edges[x].push_back(y), edges[y].push_back(x);
}
dfs1(1, -1);
dfs2(1);
if (dp[1][0] == INF && dp[1][1] == -INF) cout << "NO";
else cout << "YES";
}
int main() {
solve();
}
E. Exclusive Multiplication
题意
对正整数 n = ∏ i = 1 m p i α i \displaystyle n=\prod_{i=1}^m p_i^{\alpha_i} n=i=1∏mpiαi,定义函数 f ( n ) f(n) f(n)为 n n n的奇数次素因子之积,即 f ( n ) = ∏ i = 1 m p i α i % 2 \displaystyle f(n)=\prod_{i=1}^m p_i^{\alpha_i\%2} f(n)=i=1∏mpiαi%2.若 n n n无奇数次素因子,定义 f ( n ) = 1 f(n)=1 f(n)=1.
给定 n ( 1 ≤ n ≤ 2 e 5 ) n\ \ (1\leq n\leq 2\mathrm{e}5) n (1≤n≤2e5)和 n n n个整数 b 1 , ⋯ , b n ( 1 ≤ b i ≤ 2 e 5 ) b_1,\cdots,b_n\ \ (1\leq b_i\leq 2\mathrm{e}5) b1,⋯,bn (1≤bi≤2e5),求 ∑ 1 ≤ i < j ≤ n f ( b i × b j ) \displaystyle\sum_{1\leq i<j\leq n}f(b_i\times b_j) 1≤i<j≤n∑f(bi×bj),答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
思路
考察 f ( n ) f(n) f(n)是否是积性函数.
对 a , b ∈ N ∗ a,b\in\mathbb{N}^* a,b∈N∗,设 a = ∏ i = 1 k 1 p i α i , b = ∏ i = 1 k 2 p i β i \displaystyle a=\prod_{i=1}^{k_1}p_i^{\alpha_i},b=\prod_{i=1}^{k_2}p_i^{\beta_i} a=i=1∏k1piαi,b=i=1∏k2piβi.
因 a a a和 b b b中的偶数次素因子对 f ( a ) f(a) f(a)和 f ( b ) f(b) f(b)无贡献,不妨将偶数次素因子除掉,只留下奇数次素因子.
设此时 a ′ = p 1 α 1 ⋯ p n α n q 1 α n + 1 ⋯ q m 1 α n + m 1 , b ′ = p 1 β 1 ⋯ p n β n r 1 β n + 1 ⋯ r m 2 β n + m 2 a'=p_1^{\alpha_1}\cdots p_{n}^{\alpha_{n}}q_1^{\alpha_{n+1}}\cdots q_{m_1}^{\alpha_{n+m_1}},b'=p_1^{\beta_1}\cdots p_n^{\beta_n}r_1^{\beta_{n+1}}\cdots r_{m_2}^{\beta_{n+m_2}} a′=p1α1⋯pnαnq1αn+1⋯qm1αn+m1,b′=p1β1⋯pnβnr1βn+1⋯rm2βn+m2,
则 f ( a ) = ∏ i = 1 n p i α i ⋅ ∏ i = 1 m 1 q i α n + i , f ( b ) = ∏ i = 1 n p i β i ⋅ ∏ i = 1 m 2 r i β n + i \displaystyle f(a)=\prod_{i=1}^{n}p_i^{\alpha_i}\cdot \prod_{i=1}^{m_1}q_i^{\alpha_{n+i}},f(b)=\prod_{i=1}^{n}p_i^{\beta_i}\cdot \prod_{i=1}^{m_2}r_i^{\beta_{n+i}} f(a)=i=1∏npiαi⋅i=1∏m1qiαn+i,f(b)=i=1∏npiβi⋅i=1∏m2riβn+i.
考察 a b ab ab中的偶数次素因子的来源:
①对 a a a与 b b b相同的素因子, a b ab ab的偶数次可能来自 a a a的偶数次 + b +b +b的偶数次或 a a a的奇数次 + b +b +b的奇数次,
其中前者在 f ( a ) f(a) f(a)和 f ( b ) f(b) f(b)中都不考虑,后者在 f ( a ) f(a) f(a)和 f ( b ) f(b) f(b)中都考虑.
②对 a a a与 b b b不同的素因子, a b ab ab中的偶数次只能来自 a a a的偶数次 + b +b +b的偶数次,两者在 f ( a ) f(a) f(a)和 f ( b ) f(b) f(b)中都不考虑.
综上 f ( a b ) = f ( a ) f ( b ) [ gcd ( f ( a ) , f ( b ) ) ] 2 f(ab)=\dfrac{f(a)f(b)}{[\gcd(f(a),f(b))]^2} f(ab)=[gcd(f(a),f(b))]2f(a)f(b),虽 f ( n ) f(n) f(n)不是积性函数,但 f ( a b ) f(ab) f(ab)可由 f ( a ) f(a) f(a)和 f ( b ) f(b) f(b)表示,可先求出 f ( n ) f(n) f(n).
因计算过程与 b i b_i bi本身无关,只与 f ( b i ) f(b_i) f(bi)有关,可读入时将 b i b_i bi变为 f ( b i ) f(b_i) f(bi),记其中最大的 f ( b i ) f(b_i) f(bi)为 m a x b maxb maxb.
为计算 a n s = ∑ 1 ≤ i < j ≤ n f ( b i × b j ) \displaystyle ans=\sum_{1\leq i<j\leq n}f(b_i\times b_j) ans=1≤i<j≤n∑f(bi×bj),先计算 ∑ d ∣ n g ( d ) d 2 \displaystyle\sum_{d\mid n}\dfrac{g(d)}{d^2} d∣n∑d2g(d),其中 g ( n ) = ∑ n = gcd ( b i , b j ) f ( b i ) f ( b j ) \displaystyle g(n)=\sum_{n=\gcd(b_i,b_j)}f(b_i)f(b_j) g(n)=n=gcd(bi,bj)∑f(bi)f(bj).
a n s ans ans中的项:
f ( b 1 b 2 ) f(b_1b_2) f(b1b2) | f ( b 1 b 3 ) f(b_1b_3) f(b1b3) | f ( b 1 b 4 ) f(b_1b_4) f(b1b4) | f ( b 1 b 5 ) f(b_1b_5) f(b1b5) | f ( b 1 b 6 ) f(b_1b_6) f(b1b6) | ⋯ \cdots ⋯ |
---|---|---|---|---|---|
f ( b 2 b 3 ) f(b_2b_3) f(b2b3) | f ( b 2 b 4 ) f(b_2b_4) f(b2b4) | f ( b 2 b 5 ) f(b_2b_5) f(b2b5) | f ( b 2 b 6 ) f(b_2b_6) f(b2b6) | ⋯ \cdots ⋯ | |
f ( b 3 b 4 ) f(b_3b_4) f(b3b4) | f ( b 3 b 5 ) f(b_3b_5) f(b3b5) | f ( b 3 b 6 ) f(b_3b_6) f(b3b6) | ⋯ \cdots ⋯ | ||
f ( b 4 b 5 ) f(b_4b_5) f(b4b5) | f ( b 4 b 6 ) f(b_4b_6) f(b4b6) | ⋯ \cdots ⋯ | |||
⋮ \vdots ⋮ | ⋱ \ddots ⋱ |
∑ d ∣ n g ( d ) d 2 \displaystyle\sum_{d\mid n}\dfrac{g(d)}{d^2} d∣n∑d2g(d)中 f ( n ) f(n) f(n)的项:
f ( b 1 b 1 ) f(b_1b_1) f(b1b1) | f ( b 1 b 2 ) f(b_1b_2) f(b1b2) | f ( b 1 b 3 ) f(b_1b_3) f(b1b3) | f ( b 1 b 4 ) f(b_1b_4) f(b1b4) | f ( b 1 b 5 ) f(b_1b_5) f(b1b5) | ⋯ \cdots ⋯ |
---|---|---|---|---|---|
f ( b 2 b 1 ) f(b_2b_1) f(b2b1) | f ( b 2 b 2 ) f(b_2b_2) f(b2b2) | f ( b 2 b 3 ) f(b_2b_3) f(b2b3) | f ( b 2 b 4 ) f(b_2b_4) f(b2b4) | f ( b 2 b 5 ) f(b_2b_5) f(b2b5) | ⋯ \cdots ⋯ |
f ( b 3 b 1 ) f(b_3b_1) f(b3b1) | f ( b 3 b 2 ) f(b_3b_2) f(b3b2) | f ( b 3 b 3 ) f(b_3b_3) f(b3b3) | f ( b 3 b 4 ) f(b_3b_4) f(b3b4) | f ( b 3 b 5 ) f(b_3b_5) f(b3b5) | ⋯ \cdots ⋯ |
f ( b 4 b 1 ) f(b_4b_1) f(b4b1) | f ( b 4 b 2 ) f(b_4b_2) f(b4b2) | f ( b 4 b 3 ) f(b_4b_3) f(b4b3) | f ( b 4 b 4 ) f(b_4b_4) f(b4b4) | f ( b 4 b 5 ) f(b_4b_5) f(b4b5) | ⋯ \cdots ⋯ |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ | ⋮ \vdots ⋮ | ⋮ \vdots ⋮ | ⋮ \vdots ⋮ | ⋱ \ddots ⋱ |
对比知 ∑ d ∣ n g ( d ) d 2 \displaystyle\sum_{d\mid n}\dfrac{g(d)}{d^2} d∣n∑d2g(d)比 a n s ans ans多了下三角形的部分和对角线,而 f ( i , i ) f(i,i) f(i,i)无奇数次素因子,故 f ( i , i ) = 1 f(i,i)=1 f(i,i)=1,进而对角线元素之和为 n n n.
故 a n s = 1 2 ( ∑ d ∣ n g ( d ) d 2 − n ) \displaystyle ans=\dfrac{1}{2}\left(\sum_{d\mid n}\dfrac{g(d)}{d^2}-n\right) ans=21⎝ ⎛d∣n∑d2g(d)−n⎠ ⎞.
考虑如何计算 g ( n ) g(n) g(n).令 G ( n ) = ∑ n ∣ d g ( d ) = ∑ n ∣ gcd ( b i , b j ) f ( b i , b j ) = [ ∑ n ∣ b i f ( b i ) ] 2 \displaystyle G(n)=\sum_{n\mid d}g(d)=\sum_{n\mid \gcd(b_i,b_j)}f(b_i,b_j)=\left[\sum_{n|b_i}f(b_i)\right]^2 G(n)=n∣d∑g(d)=n∣gcd(bi,bj)∑f(bi,bj)=⎣ ⎡n∣bi∑f(bi)⎦ ⎤2.
设 f c n t [ i ] fcnt[i] fcnt[i]表示 s . t . f ( b j ) = i \ s.t.\ f(b_j)=i s.t. f(bj)=i的 b j b_j bj的个数,则 G ( n ) G(n) G(n)可通过 f c n t [ i ] fcnt[i] fcnt[i]求出.
由Mobius反演: g ( d ) = ∑ d ∣ n μ ( n d ) G ( n ) \displaystyle g(d)=\sum_{d\mid n}\mu\left(\dfrac{n}{d}\right)G(n) g(d)=d∣n∑μ(dn)G(n).
代码 -> 2022CCPC黑龙江省赛-E(莫反)
const int MAXN = 2e5 + 5;
const int MOD = 1e9 + 7;
int n;
int fb[MAXN]; // fb[i]=f(b_i)
int fcnt[MAXN]; // cnt[i]表示 s.t. f(b_j)=i的b_j的个数
int primes[MAXN], cnt = 0;
bool vis[MAXN];
int mu[MAXN];
int G[MAXN]; // G(n)=Σ_{n|d} g(d)
int g[MAXN]; // g(n)=Σ_{n=gcd(b_i,b_j)} f(b_i)×f(b_j)
void init() { // 预处理mu[]
mu[1] = 1;
for (int i = 2; i < MAXN; i++) {
if (!vis[i]) primes[cnt++] = i, mu[i] = -1;
for (int j = 0; primes[j] * i < MAXN; j++) {
vis[primes[j] * i] = true;
if (i % primes[j] == 0) break;
mu[primes[j] * i] = -mu[i];
}
}
for (int i = 1; i < MAXN; i++) mu[i] = (mu[i] + MOD) % MOD;
}
int inv(int x) { return qpow(x, MOD - 2, MOD); }
void solve() {
init();
cin >> n;
for (int i = 1; i <= n; i++) fb[i] = 1; // 初始化
int maxnum = 0;
for (int i = 1; i <= n; i++) {
int b; cin >> b;
for (int d = 2; d <= b / d; d++) { // 枚举b的约数d
if (b % d == 0) {
int s = 0; // 素因子的次数
while (b % d == 0) b /= d, s++;
if (s & 1) fb[i] = (ll)fb[i] * d % MOD; // s为奇数时才对f(b)有贡献
}
}
if (b > 1) fb[i] = (ll)fb[i] * b % MOD; // 大于sqrt(b)的素因子
fcnt[fb[i]]++;
maxnum = max(maxnum, fb[i]);
}
for (int d = 1; d <= maxnum; d++) { // 枚举约数d
for (int j = d; j <= maxnum; j += d) // 枚举d的倍数
if (fcnt[j]) G[d] = ((ll)G[d] + (ll)fcnt[j] * j % MOD) % MOD;
G[d] = (ll)G[d] * G[d] % MOD;
}
for (int d = 1; d <= maxnum; d++) { // 枚举约数d
for (int j = d; j <= maxnum; j += d) // 枚举d的倍数
g[d] = ((ll)g[d] + (ll)mu[j / d] * G[j] % MOD) % MOD; // 反演出g[]
}
int ans = 0;
for (int d = 1; d <= maxnum; d++)
ans = ((ll)ans + (ll)g[d] * inv(d) % MOD * inv(d) % MOD) % MOD;
ans = (ll)((ans - n) % MOD + MOD) % MOD * inv(2) % MOD;
cout << ans;
}
int main() {
solve();
}