2022CCPC黑龙江省赛题解ACEFGHIL

2022CCPC黑龙江省赛题解ACEFGHIL

题目链接:
https://codeforces.com/gym/103688

代码链接:
https://hytidel.lanzoub.com/b031d28yh
密码:evfs

I. Equal Sum Arrays

题意

∀ n ∈ N ∗ \forall n\in\mathbb{N}^* nN,定义函数 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  (1k20),求 f ( k ) f(k) f(k).

思路

f ( n ) f(n) f(n),转化为在 n n n个小球排成一行的 ( n − 1 ) (n-1) (n1)个空隙中插板,每个空隙可插可不插,显然 f ( n ) = 2 n − 1 f(n)=2^{n-1} f(n)=2n1.

代码 -> 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  (1t1e5)组测试数据.每组测试数据输入四个整数 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  (1a1,a2,b1,b27,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  (3n1e5)的只包含’(‘、’-‘、’)‘且符合括号匹配规则的字符串,下标从 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[lr].从左往右阅读,遇到’-‘或’)‘时直接阅读;遇到’('时先找到与其匹配的右括号,再递归到它们之间的区间即可.注意边界有 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++(n1)=2n(n1),会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  (hb)的书架, n n n本A型书和 m m m本B型书.现将书按高度非降序依次放在书架上,即B型书都在A型书的右边.现可从最右边选 k    ( 0 ≤ k ≤ m − 1 ) k\ \ (0\leq k\leq m-1) k  (0km1)本B型书横着放在竖着放的书的上方.求将所有书放好至少需占用书架的宽度.

t    ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t  (1t1000)组测试数据.每组测试数据输入四个整数 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  (1a<bh1e6,1n,m1e9).

思路

在这里插入图片描述

设竖着放的B型书剩下 x x x本,则可放剩下的 ( m − x ) (m-x) (mx)本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] [(ba)bn+(hb)bn+x]本,注意此处可能爆int.

因为竖着放的B型书越少,则越可能放不下,即具有二段性.二分 x x x,判断能放得下的B型书数是否 ≥ m \geq m m即可.注意至多选 ( m − 1 ) (m-1) (m1)本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  (1ls)的前缀,则 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  (1t5e5)组测试数据.每次测试数据第一行输入字符串 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  (1l1,l2s=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| l1l2个单位,则合法的循环位移长度为 k ∣ l 1 − l 2 ∣   m o d   n k|l_1-l_2|\ \mathrm{mod}\ n kl1l2 mod n.取 d = gcd ⁡ ( ∣ l 1 − l 2 ∣ , n ) d=\gcd(|l_1-l_2|,n) d=gcd(l1l2,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 1n.每颗珍珠上有一个非负整数 c i c_i ci,每次操作可选择一颗满足 c i ≥ 1 c_i\geq 1 ci1的珍珠 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  (1n2000).第二行输入 n n n个整数 c 1 , ⋯   , c n    ( 0 ≤ c i ≤ 2000 ) c_1,\cdots,c_n\ \ (0\leq c_i\leq 2000) c1,,cn  (0ci2000).

输出两个整数,第一个表示最多能移除多少颗珍珠,第二个表示其方案数,答案对 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=1kcpin.

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) cpidis(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. cpidis(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=1kcpi>i=1kdis(pi)=n,矛盾.

故每次操作时都选中一个满足 c p i ≤ d i s ( p i ) c_{p_i}\leq dis(p_i) cpidis(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 1n n n n个节点的树,其中第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)个节点有权值 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,vA  (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,vB  (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  (1n1e5).第二行输入 n n n个整数 a i    ( 1 ≤ a i ≤ n ) a_i\ \ (1\leq a_i\leq n) ai  (1ain).接下来 ( n − 1 ) (n-1) (n1)行每行输入两个整数 x , y    ( 1 ≤ x , y ≤ n ) x,y\ \ (1\leq x,y\leq n) x,y  (1x,yn),表示节点 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,vA  (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,vB  (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=1mpiα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=1mpiα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  (1n2e5) 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  (1bi2e5),求 ∑ 1 ≤ i < j ≤ n f ( b i × b j ) \displaystyle\sum_{1\leq i<j\leq n}f(b_i\times b_j) 1i<jnf(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,bN,设 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=1k1piαi,b=i=1k2piβ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α1pnαnq1αn+1qm1αn+m1,b=p1β1pnβnr1βn+1rm2β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=1npiαii=1m1qiαn+i,f(b)=i=1npiβii=1m2riβ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=1i<jnf(bi×bj),先计算 ∑ d ∣ n g ( d ) d 2 \displaystyle\sum_{d\mid n}\dfrac{g(d)}{d^2} dnd2g(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} dnd2g(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} dnd2g(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 dnd2g(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)=ndg(d)=ngcd(bi,bj)f(bi,bj)= nbif(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)=dnμ(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();
}


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值