杂题选做(2023.9.28-2023.10.1)
你有 n n n i i i 1 ⋯ A i 1\cdots A_i 1 ⋯ A i  A i + 1 ⋯ 2 A i A_i+1 \cdots 2A_i A i  + 1 ⋯ 2 A i  2 A i + 1 ⋯ 3 A i 2A_i+1 \cdots 3A_i 2 A i  + 1 ⋯ 3 A i  
每天你会选一个来玩的人,给他颁个奖,如果没人来玩,你就不颁奖。
你要给每个人都颁 K K K 
1   ≤   N   ≤   18 1\ \le\ N\ \le\ 18 1   ≤   N   ≤   1 8 1   ≤   K   ≤   1 0 5 1\ \le\ K\ \le\ 10^5 1   ≤   K   ≤   1 0 5 1   ≤   A i   ≤   1 0 5 1\ \le\ A_i\ \le\ 10^5 1   ≤   A i    ≤   1 0 5 
每天只能选择一个人,每个人需要被选择 k k k 
可以将每一个人拆成 K K K 
注意到一点就是我们的答案不会很大,每一个人花费 2 × K 2 \times K 2 × K 
证明可以考虑分类讨论:如果 k ≤ a i k \le a_i k ≤ a i  k > a i k > a_i k > a i  
所以做法就是考虑二分答案,然后建图,判断图是否有完美匹配。
但是如果我们直接建边的话复杂度就肯定爆了,可以考虑使用霍尔定理判断是否合法。
霍尔定理即:若一个二分图有完美匹配,当且仅当对于任意一个左部点的点集 S S S T T T ∣ S ∣ ≤ ∣ T ∣ |S| \le |T| ∣ S ∣ ≤ ∣ T ∣ 
因为每一个人拆成的 K K K 2 N 2^N 2 N 
判断与其相邻的右部点的点集大小,可以考虑对于每一个时刻找出 S i S_i S i  S i S_i S i  i i i i i i T T T S i S_i S i  T T T 
但是交集不为空这个东西就很难以表示,所以就容斥一下,可以考虑维护 T i T_i T i  T i T_i T i  i i i T i T_i T 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 31 32 33 34 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 1e6 +5 ;int  n,k,S[40  * N],f[N],a[N];bool  chk (int  mid) 	for (int  i=0 ; i<(1 <<n); i++)	f[i] = 0 ; 	for (int  i=1 ; i<=mid; i++)	f[((1 <<n)-1 ) ^ S[i]]++;  	for (int  i=1 ; i<=n; i++){ 		for (int  j=0 ; j<(1 <<n); j++){ 			if (!((j >> (i-1 )) & 1 ))	f[j] += f[j^(1 <<(i-1 ))]; 		} 	} 	for (int  i=0 ; i<(1 <<n); i++){ 		if (mid - f[i < __builtin_popcount(i) * k)	return  false ; 	} 	return  true ; } int  main () 	scanf ("%d%d" ,&n,&k); 	for (int  i=1 ; i<=n; i++)	scanf ("%d" ,&a[i]); 	for (int  i=1 ; i<=2 *n*k; i++){ 		for (int  j=1 ; j<=n; j++){ 			if (((i + a[j] - 1 ) / a[j]) % 2  == 1 )	S[i] |= (1 <<(j-1 )); 		} 	} 	int  l = 1 ,r = 2  * n * k,ans = 1 ; 	while (l <= r){ 		int  mid = (l + r) >> 1 ; 		if (chk (mid))	ans = mid,r = mid - 1 ; 		else 	l = mid + 1 ; 	} 	printf ("%d\n" ,ans); 	return  0 ; } 
 
密码学家正在尝试破解一种叫 BSA 的密码。
他发现,在破解一条消息的同时,他还需要回答这样一种问题:
给出 a , b , d a,b,d a , b , d 1 ≤ x ≤ a 1 \leq x \leq a 1 ≤ x ≤ a 1 ≤ y ≤ b 1 \leq y \leq b 1 ≤ y ≤ b gcd  ( x , y ) = d \gcd(x,y)=d g cd( x , y ) = d ( x , y ) (x,y) ( x , y ) 
因为要解决的问题实在太多了,他便过来寻求你的帮助。
对于全部的测试点,保证 1 ≤ n ≤ 5 × 1 0 4 1 \leq n \leq 5 \times 10^4 1 ≤ n ≤ 5 × 1 0 4 1 ≤ d ≤ a , b ≤ 5 × 1 0 4 1 \leq d \leq a,b \leq 5 \times 10^4 1 ≤ d ≤ a , b ≤ 5 × 1 0 4 
(一道莫反的板子题)
话不多说,考虑推式子:
a n s = ∑ x = 1 n ∑ y = 1 m [ gcd  ( x , y ) = d ] = ∑ x = 1 ⌊ n d ⌋ ∑ y = 1 ⌊ m d ⌋ [ gcd  ( x , y ) = 1 ] = ∑ k = 1 ⌊ min  ( a , b ) d ⌋ μ ( k ) ∑ x = 1 ⌊ n d k ⌋ ∑ y = 1 ⌊ m d k ⌋ 1 = ∑ k = 1 ⌊ min  ( a , b ) d ⌋ μ ( k ) ⌊ n d k ⌋ ⌊ m d k ⌋ \begin{aligned}
ans 
&= \sum_{x=1}^n \sum_{y=1}^m [\gcd(x,y)=d] \\
&= \sum_{x=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{y=1}^{\lfloor \frac{m}{d} \rfloor} [\gcd(x,y) = 1] \\
&= \sum_{k=1}^{\lfloor \frac{\min(a,b)}{d} \rfloor} \mu(k) \sum_{x=1}^{\lfloor \frac{n}{dk} \rfloor} \sum_{y=1}^{\lfloor \frac{m}{dk} \rfloor} 1 \\
&= \sum_{k=1}^{\lfloor \frac{\min(a,b)}{d} \rfloor} \mu(k) \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor
\end{aligned}
 a n s  = x = 1 ∑ n  y = 1 ∑ m  [ g cd( x , y ) = d ] = x = 1 ∑ ⌊ d n  ⌋  y = 1 ∑ ⌊ d m  ⌋  [ g cd( x , y ) = 1 ] = k = 1 ∑ ⌊ d m i n ( a , b )  ⌋  μ ( k ) x = 1 ∑ ⌊ d k n  ⌋  y = 1 ∑ ⌊ d k m  ⌋  1 = k = 1 ∑ ⌊ d m i n ( a , b )  ⌋  μ ( k ) ⌊ d k n  ⌋ ⌊ d k m  ⌋  
对最后的这个式子直接整除分块,使用线性筛预处理 μ \mu μ 
设所有数均同阶,则复杂度为 O ( n n ) O(n\sqrt{n}) O ( n 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 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 1e5 +5 ;int  mu[N],prime[N],s[N],tot;bool  flag[N];void  pre_work (int  n) 	mu[1 ] = 1 ; 	for (int  i=2 ; i<=n; i++){ 		if (!flag[i]){ 			prime[++tot] = i; 			mu[i] = -1 ; 		} 		for (int  j=1 ; j<=tot && i * prime[j] <= n; j++){ 			flag[i * prime[j]] = true ; 			if (i % prime[j] == 0 ){ 				mu[i * prime[j]] = 0 ; 				break ; 			} 			else 	mu[i * prime[j]] = -mu[i]; 		} 	} 	 	for (int  i=1 ; i<=n; i++)	s[i] = s[i-1 ] + mu[i]; } int  main () 	pre_work (50000 ); 	int  T;scanf ("%d" ,&T); 	while (T--){ 		int  a,b,k;scanf ("%d%d%d" ,&a,&b,&k); 		a /= k,b /= k; 		int  ans = 0 ; 		for (int  l=1 ,r=0 ; l<=min (a,b); l = r + 1 ){ 			r = min (a/(a/l),b/(b/l)); 			ans += (s[r] - s[l-1 ]) * (a/l) * (b/l); 		} 		printf ("%d\n" ,ans); 	} 	return  0 ; } 
 
给定一个长度为 N N N A 1 , A 2 , A 3 , … , A N A_1, A_2, A_3, \ldots, A_N A 1  , A 2  , A 3  , … , A N  ∑ i = 1 N ∑ j = i + 1 N l c m ( A i , A j ) \sum_{i=1}^{N}\sum_{j=i+1}^{N}\mathrm{lcm}(A_i,A_j) ∑ i = 1 N  ∑ j = i + 1 N  l c m ( A i  , A j  ) 998244353 998244353 9 9 8 2 4 4 3 5 3 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1 ≤ N ≤ 2 × 1 0 5 1 ≤ A i ≤ 1 0 6 1 \leq A_i \leq 10^6 1 ≤ A i  ≤ 1 0 6 
1   ≤   N   ≤   2 ⋅ 1 0 4 1\ \leq\ N\ \leq\ 2\cdot 10^4 1   ≤   N   ≤   2 ⋅ 1 0 4 1   ≤   A i   ≤   1 ⋅ 1 0 6 1\ \leq\ A_i\ \leq\ 1\cdot 10^6 1   ≤   A i    ≤   1 ⋅ 1 0 6 
显然要使用莫比乌斯反演来解决这个问题。
首先因为 j j j i + 1 i+1 i + 1 
∑ i = 1 n ∑ j = 1 n lcm ( A i , A j ) − ∑ i = 1 n lcm ( A i , A i ) 2 \frac{\sum \limits_{i=1}^n \sum \limits_{j=1}^n \textup{lcm}(A_i,A_j) - \sum \limits_{i=1}^n \textup{lcm}(A_i,A_i)}{2}
 2 i = 1 ∑ n  j = 1 ∑ n  lcm ( A i  , A j  ) − i = 1 ∑ n  lcm ( A i  , A i  )  
下面我们的问题就显然是求前面这一坨,因为我们直接对下标做就很麻烦,所以考虑对于值域套莫反,也就是设 b i b_i b i  i i i 
a n s = ∑ i = 1 V ∑ j = 1 V b i × b j × i × j gcd  ( i , j ) = ∑ d = 1 V ∑ i = 1 ⌊ V d ⌋ ∑ j = 1 ⌊ V d ⌋ [ gcd  ( i , j ) = 1 ] 1 d b i d × b j d × i d × j d = ∑ d = 1 V 1 d ∑ k = 1 ⌊ V d ⌋ μ ( k ) ∑ i = 1 ⌊ V d k ⌋ ∑ j = 1 ⌊ V d k ⌋ b i k d × b j k d × i k d × j k d = ∑ d = 1 V d ∑ k = 1 ⌊ V d ⌋ k 2 μ ( k ) ∑ i = 1 ⌊ V d k ⌋ ∑ j = 1 ⌊ V d k ⌋ b i k d × b j k d × i × j \begin{aligned}
ans 
&= \sum_{i=1}^V \sum_{j=1}^V b_i \times b_j \times \frac{i \times j}{\gcd(i,j)} \\
&= \sum_{d=1}^V \sum_{i=1}^{\lfloor \frac{V}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{V}{d} \rfloor} [\gcd(i,j)=1] \frac{1}{d} b_{id} \times b_{jd} \times id \times jd \\
&= \sum_{d=1}^V \frac{1}{d} \sum_{k=1}^{\lfloor \frac{V}{d} \rfloor} \mu(k) \sum_{i=1}^{\lfloor \frac{V}{dk} \rfloor} \sum_{j=1}^{\lfloor \frac{V}{dk} \rfloor} b_{ikd} \times b_{jkd} \times ikd \times jkd \\
&= \sum_{d=1}^V d \sum_{k=1}^{\lfloor \frac{V}{d} \rfloor} k^2\mu(k) \sum_{i=1}^{\lfloor \frac{V}{dk} \rfloor} \sum_{j=1}^{\lfloor \frac{V}{dk} \rfloor} b_{ikd} \times b_{jkd} \times i \times j
\end{aligned}
 a n s  = i = 1 ∑ V  j = 1 ∑ V  b i  × b j  × g cd( i , j ) i × j  = d = 1 ∑ V  i = 1 ∑ ⌊ d V  ⌋  j = 1 ∑ ⌊ d V  ⌋  [ g cd( i , j ) = 1 ] d 1  b i d  × b j d  × i d × j d = d = 1 ∑ V  d 1  k = 1 ∑ ⌊ d V  ⌋  μ ( k ) i = 1 ∑ ⌊ d k V  ⌋  j = 1 ∑ ⌊ d k V  ⌋  b i k d  × b j k d  × i k d × j k d = d = 1 ∑ V  d k = 1 ∑ ⌊ d V  ⌋  k 2 μ ( k ) i = 1 ∑ ⌊ d k V  ⌋  j = 1 ∑ ⌊ d k V  ⌋  b i k d  × b j k d  × i × j  
可以考虑令 T = d k T = dk T = d k 
a n s = ∑ T = 1 V T ∑ d ∣ T T d μ ( T d ) ( ∑ i = 1 ⌊ V T ⌋ b i T × i ) 2 \begin{aligned}
ans 
&= \sum_{T=1}^V T \sum_{d | T} \frac{T}{d}\mu(\frac{T}{d}) (\sum_{i=1}^{\lfloor \frac{V}{T} \rfloor} b_{iT} \times i)^2
\end{aligned}
 a n s  = T = 1 ∑ V  T d ∣ T ∑  d T  μ ( d T  ) ( i = 1 ∑ ⌊ T V  ⌋  b i T  × i ) 2  
这样的话对于:∑ d ∣ T T d μ ( T d ) \sum_{d|T} \frac{T}{d} \mu(\frac{T}{d}) ∑ d ∣ T  d T  μ ( d T  ) μ \mu μ T T T O ( V log  V ) O(V \log V) O ( V log  V ) 
对于 ∑ i = 1 ⌊ V T ⌋ b i T × i \sum_{i=1}^{\lfloor \frac{V}{T} \rfloor} b_{iT} \times i ∑ i = 1 ⌊ T V  ⌋  b i T  × i i i i O ( V log  V ) O(V \log V) O ( V log  V ) 
所以总时间复杂度就是 O ( V log  V ) O(V \log V) O ( V log  V ) 
 点击查看代码  
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 #include <bits/stdc++.h>  #define  int long long using  namespace  std;const  int  N = 2e6 +5 ;const  int  MOD = 998244353 ;int  tot,a[N],b[N],prime[N],mu[N],f[N];bool  flag[N];void  add (int  &a,int  b) 	a = ((a + b)%MOD+MOD)%MOD; } int  power (int  a,int  b) 	int  res = 1 ; 	while (b){ 		if (b & 1 )	res = res * a % MOD; 		a = a * a % MOD; 		b >>= 1 ; 	} 	return  res; } void  pre_work (int  n) 	mu[1 ] = 1 ; 	for (int  i=2 ; i<=n; i++){ 		if (!flag[i]){ 			prime[++tot] = i; 			mu[i] = -1 ; 		} 		for (int  j=1 ; j<=tot && i * prime[j] <= n; j++){ 			flag[i * prime[j]] = true ; 			if (i % prime[j] == 0 ){ 				mu[i * prime[j]] = 0 ; 				break ; 			} 			else 	mu[i * prime[j]] = -mu[i]; 		} 	} 	 	for (int  i=1 ; i<=n; i++){ 		for (int  j=i; j<=n; j+=i){ 			add (f[j],mu[i] * i % MOD); 		} 	} } signed  main () 	pre_work (1000000 ); 	int  n;scanf ("%lld" ,&n); 	for (int  i=1 ; i<=n; i++){ 		scanf ("%lld" ,&a[i]); 		b[a[i]]++; 	} 	 	int  ans = 0 ; 	int  V = 1e6 ; 	for (int  T=1 ; T<=V; T++){ 		int  tmp = 0 ; 		for (int  i=1 ; i<=V/T; i++)	tmp = (tmp + b[i * T] * i % MOD)%MOD; 		tmp = tmp * tmp % MOD; 		ans = ((ans + f[T] * T %MOD * tmp%MOD)%MOD+MOD)%MOD; 	} 	 	for (int  i=1 ; i<=n; i++)	ans = ((ans - a[i])%MOD + MOD)%MOD; 	 	printf ("%lld\n" ,ans * power (2 ,MOD-2 ) % MOD); 	return  0 ; } 
 
有一张 n × m n\times m n × m i i i j j j 1 ≤ i ≤ n 1\le i\le n 1 ≤ i ≤ n 1 ≤ j ≤ m 1\le j\le m 1 ≤ j ≤ m i i i j j j a a a a a a 
对于全部数据,1 ≤ n , m ≤ 1 0 5 1\le n,m\le 10^5 1 ≤ n , m ≤ 1 0 5 1 ≤ Q ≤ 2 × 1 0 4 1\le Q\le 2\times 10^4 1 ≤ Q ≤ 2 × 1 0 4 
这个东西直觉上是莫反,但是有一个小于等于 a a a 
下文推导默认 n < m n < m n < m n , m n,m n , m 
a n s = ∑ i = 1 n ∑ j = 1 m σ 1 ( gcd  ( i , j ) ) = ∑ d = 1 n ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd  ( i , j ) = 1 ] σ 1 ( d ) = ∑ d = 1 n σ 1 ( d ) ∑ k = 1 ⌊ n d ⌋ μ ( k ) ⌊ n d k ⌋ ⌊ m d k ⌋ \begin{aligned}
ans 
&=\sum_{i=1}^n \sum_{j=1}^m \sigma_1(\gcd(i,j)) \\
&= \sum_{d=1}^n \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} [\gcd(i,j)=1] \sigma_1(d) \\
&= \sum_{d=1}^n \sigma_1(d) \sum_{k=1}^{\lfloor \frac{n}{d} \rfloor} \mu(k) \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor
\end{aligned}
 a n s  = i = 1 ∑ n  j = 1 ∑ m  σ 1  ( g cd( i , j ) ) = d = 1 ∑ n  i = 1 ∑ ⌊ d n  ⌋  j = 1 ∑ ⌊ d m  ⌋  [ g cd( i , j ) = 1 ] σ 1  ( d ) = d = 1 ∑ n  σ 1  ( d ) k = 1 ∑ ⌊ d n  ⌋  μ ( k ) ⌊ d k n  ⌋ ⌊ d k m  ⌋  
可以考虑设 T = d k T = dk T = d k 
a n s = ∑ T = 1 n ⌊ n T ⌋ ⌊ m T ⌋ ∑ d ∣ T σ 1 ( d ) μ ( T d ) \begin{aligned}
ans 
&= \sum_{T=1}^n \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{d \mid T} \sigma_1(d) \mu(\frac{T}{d}) 
\end{aligned}
 a n s  = T = 1 ∑ n  ⌊ T n  ⌋ ⌊ T m  ⌋ d ∣ T ∑  σ 1  ( d ) μ ( d T  )  
如果我们可以维护好后面这一坨的值我们就可以直接整除分块。
可以考虑离线,将询问按照 a a a σ 1 ( d ) \sigma_1(d) σ 1  ( d ) σ 1 ( d ) \sigma_1(d) σ 1  ( d ) σ 1 ( d ) ≤ a \sigma_1(d) \le a σ 1  ( d ) ≤ a d d d T T T σ 1 ( d ) μ ( T d ) \sigma_1(d) \mu(\frac{T}{d}) σ 1  ( d ) μ ( d T  ) T T T 
复杂度 O ( q n log  n + n log  2 n ) O(q \sqrt{n} \log n + n \log^2 n) O ( q n  log  n + n log  2 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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <bits/stdc++.h>  #define  int long long #define  PII pair<int,int>  using  namespace  std;const  int  N = 2e5 +5 ;const  int  MOD = 1ll <<31 ;struct  node {	int  n,m,a,id; }p[N]; int  prime[N],tot,ans[N],sum[N],mu[N];bool  flag[N];PII w[N]; void  pre_work (int  n) 	mu[1 ] = 1 ; 	for (int  i=2 ; i<=n; i++){ 		if (!flag[i]){ 			prime[++tot] = i; 			mu[i] = -1 ; 		} 		for (int  j=1 ; j<=tot && i * prime[j] <= n; j++){ 			flag[i * prime[j]] = true ; 			if (i % prime[j] == 0 ){ 				mu[i * prime[j]] = 0 ; 				break ; 			} 			else  mu[i * prime[j]] = -mu[i]; 		} 	} 	for (int  i=1 ; i<=n; i++){ 		w[i].second = i; 		for (int  j=i; j<=n; j+=i){ 			w[j].first += i; 		} 	} } bool  cmp (node a,node b) 	return  a.a < b.a; } bool  cmp2 (PII a,PII b) 	return  a.first < b.first; } int  lowbit (int  x) 	return  x & (-x); } void  modify (int  x,int  val) 	for (;x <= 100000 ; x+=lowbit (x))	sum[x] += val; } int  query (int  x) 	int  ans = 0 ; 	for (;x; x -= lowbit (x))	ans += sum[x]; 	return  ans; } int  query (int  l,int  r) 	return  query (r) - query (l-1 ); } signed  main () 	pre_work (100000 ); 	 	sort (w+1 ,w+100000 +1 ,cmp2); 	int  now = 1 ; 	int  q;scanf ("%lld" ,&q); 	for (int  i=1 ; i<=q; i++)	scanf ("%lld%lld%lld" ,&p[i].n,&p[i].m,&p[i].a),p[i].id = i; 	 	sort (p+1 ,p+q+1 ,cmp); 	 	for (int  i=1 ; i<=q; i++){ 		while (now <= 100000  && w[now].first <= p[i].a){ 			int  tmp = w[now].second; 			for (int  j=tmp; j<=100000 ; j+=tmp){ 				modify (j,w[now].first * mu[j/tmp]); 			} 			++now; 		} 		 		if (p[i].n > p[i].m)	swap (p[i].n,p[i].m); 		int  n = p[i].n,m = p[i].m; 		for (int  l=1 ,r=0 ; l<=n; l=r+1 ){ 			r = min (n/(n/l),m/(m/l)); 			ans[p[i].id] = (ans[p[i].id] + (n/l) * (m/l) * query (l,r)); 		} 	} 	 	for (int  i=1 ; i<=q; i++)	printf ("%lld\n" ,(ans[i] + MOD)%MOD); 	return  0 ; } 
 
给定一棵有根树,令根节点为 1 1 1 n n n 
每个节点有五种形式:AND 表示对两个子节点进行与运算,OR 表示对两个子节点进行或运算,XOR 表示对两个子节点进行异或运算,NOT 表示对子节点(只有一个)进行非运算。特殊地,IN 表示这个节点是叶子节点,它的初值(0 / 1 0/1 0 / 1 
现在,你要依次对每个叶子节点进行改变,改变其 0 / 1 0/1 0 / 1 0 0 0 1 1 1 
2 ≤ n ≤ 1 0 6 2 \le n \le 10^6 2 ≤ n ≤ 1 0 6 
若当前节点为 XOR 或 NOT,则叶子节点只要改变当前节点的值必然改变。
若当前节点为 AND,则只有两个叶子节点同时为 1 1 1 0 , 1 0,1 0 , 1 0 0 0 
若当前节点为 OR,则只有两个叶子节点同时为 0 0 0 0 , 1 0,1 0 , 1 1 1 1 
如果改变节点 x x x 1 1 1 x x x 1 1 1 
可以按照是否合法的情况从上到下传递标记,也就是对于每一个节点维护 f i f_i f i  i i i 
具体实现就是先从下到上 dfs 获得每一个节点的值,然后从上到达 dfs 得到 f i f_i f i  
时间复杂度 O ( n ) O(n) O ( 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 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 2e6 +5 ;int  ls[N],rs[N],val[N],opt[N];char  s[N];bool  flag[N];void  dfs1 (int  now) 	if (ls[now])	dfs1 (ls[now]); 	if (rs[now])	dfs1 (rs[now]); 	if (opt[now] == 1 )	val[now] = val[ls[now]] & val[rs[now]]; 	if (opt[now] == 2 )	val[now] = val[ls[now]] | val[rs[now]]; 	if (opt[now] == 3 )	val[now] = val[ls[now]] ^ val[rs[now]]; 	if (opt[now] == 4 )	val[now] = val[ls[now]] ^ 1 ; } void  dfs2 (int  now) 	flag[now] = true ; 	if (opt[now] == 1 ){ 		if (val[ls[now]] == 1  && val[rs[now]] == 1 )	dfs2 (ls[now]),dfs2 (rs[now]); 		if (val[ls[now]] == 0  && val[rs[now]] == 1 )	dfs2 (ls[now]); 		if (val[ls[now]] == 1  && val[rs[now]] == 0 )	dfs2 (rs[now]); 	} 	if (opt[now] == 2 ){ 		if (val[ls[now]] == 0  && val[rs[now]] == 0 )	dfs2 (ls[now]),dfs2 (rs[now]); 		if (val[ls[now]] == 0  && val[rs[now]] == 1 )	dfs2 (rs[now]); 		if (val[ls[now]] == 1  && val[rs[now]] == 0 )	dfs2 (ls[now]); 	} 	if (opt[now] == 3 )	dfs2 (ls[now]),dfs2 (rs[now]); 	if (opt[now] == 4 )	dfs2 (ls[now]); } int  main () 	int  n;scanf ("%d" ,&n); 	for (int  i=1 ; i<=n; i++){ 		scanf ("%s" ,s+1 ); 		if (s[1 ] == 'A' ){ 			opt[i] = 1 ; 			scanf ("%d%d" ,&ls[i],&rs[i]); 		} 		else  if (s[1 ] == 'O' ){ 			opt[i] = 2 ; 			scanf ("%d%d" ,&ls[i],&rs[i]); 		} 		else  if (s[1 ] == 'X' ){ 			opt[i] = 3 ; 			scanf ("%d%d" ,&ls[i],&rs[i]); 		} 		else  if (s[1 ] == 'N' ){ 			opt[i] = 4 ; 			scanf ("%d" ,&ls[i]); 		} 		else  if (s[1 ] == 'I' ){ 			opt[i] = 5 ;scanf ("%d" ,&val[i]); 		} 	} 	 	dfs1 (1 ); 	dfs2 (1 ); 	for (int  i=1 ; i<=n; i++){ 		if (opt[i] == 5 ){ 			if (flag[i])	printf ("%d" ,val[1 ] ^ 1 ); 			else 	printf ("%d" ,val[1 ]); 		} 	} 	return  0 ; } 
 
一个 tournament \texttt{tournament} tournament u , v ( u ≠ v ) u,v (u\neq v) u , v ( u  = v ) u u u v v v v v v u u u 
给你一个 tournament \texttt{tournament} tournament 3 3 3 
1 ≤ n ≤ 5000 1 \le n \le 5000 1 ≤ n ≤ 5 0 0 0 
注意到这是一张竞赛图,也就是说若存在环则必然存在三元环。
这个结论可以通过数学归纳法证明,可以自行画图证明。
所以可以直接跑一遍 d f s dfs d f s u u u u u u f a u fa_u f a u  v v v 
因为我们访问过一个点之后就没有必要再访问一次,所以时间复杂度为 O ( n + m ) O(n+m) O ( n + m ) 
 点击查看代码  
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 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 5005 ;int  n,sz = -1 ,a[N];char  G[N][N];bool  flag = false ,vis[N],tag[N];bool  chk (int  a,int  b,int  c) 	if (G[a][b] == '1'  && G[b][c] == '1'  && G[c][a] == '1' )	return  true ; 	return  false ; } void  dfs (int  now,int  fath) 	if (flag)	return ; 	vis[now] = tag[now] = true ; 	for (int  i=1 ; i<=n; i++){ 		if (G[now][i] == '0' )	continue ; 		if (!tag[i])	dfs (i,now); 		else  if (vis[i]){ 			printf ("%d %d %d\n" ,now,i,fath); 			flag = true ; 		} 		if (flag)	return ; 	} 	vis[now] = false ; } int  main () 	scanf ("%d" ,&n); 	for (int  i=1 ; i<=n; i++)	scanf ("%s" ,G[i]+1 ); 	for (int  i=1 ; i<=n; i++)	if (!tag[i])	dfs (i,0 ); 	if (!flag)	printf ("-1\n" ); 	return  0 ; } 
 
在 ( 0 , 0 ) ∼ ( n , m ) (0, 0) \sim (n, m) ( 0 , 0 ) ∼ ( n , m ) ( x , y ) (x, y) ( x , y ) x 2 − x 1 y 2 − y 1 = a b \frac{x_2 - x_1}{y_2 - y_1} = \frac{a}{b} y 2  − y 1  x 2  − x 1   = b a  ( x , y ) (x, y) ( x , y ) ( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2) ( x 1  , y 1  , x 2  , y 2  ) 
首先可以将 a , b a,b a , b gcd  ( a , b ) \gcd(a,b) g cd( a , b ) ( a , b ) (a,b) ( a , b ) 
设为 t t t t = min  ( ⌊ n a ⌋ , ⌊ m b ⌋ ) t = \min(\lfloor \frac{n}{a} \rfloor,\lfloor \frac{m}{b} \rfloor) t = min ( ⌊ a n  ⌋ , ⌊ b m  ⌋ ) 
所以为了让中心点尽可能靠近 ( x , y ) (x,y) ( x , y ) 
也就是每次从 ( x , y ) (x,y) ( x , y ) 1 1 1 ( x , y ) (x,y) ( x , y ) 
有可能存在边长为奇数的情况,这种情况为了使得排列的字典序最小,要尽可能地先扩展 x 1 , y 1 x_1,y_1 x 1  , y 1  
因为必然有解,所以如果某一边不能扩展了,那么就把剩下的边长放到另一边即可。
因为注意到横纵坐标的处理方式相同,所以可以直接写一个函数分别处理即可。
 点击查看代码  
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 #include <bits/stdc++.h>  #define  PII pair<int,int>  using  namespace  std;int  n,m,x,y,a,b;PII get (int  x,int  a,int  n)  {	int  x1 = 0 ,x2 = 0 ; 	if (x - (a/2 ) >= 0  && x + (a / 2 ) <= n){ 		x1 = x - (a / 2 ); 		x2 = x + (a / 2 ); 		if (a % 2  == 1 ){ 			if (x1 != 0 )	x1--; 			else  x2++; 		} 	}  	else { 		if (x - (a / 2 ) < 0 )	x1 = 0 ,a -= x,x2 = x + a; 		else  if (x + (a / 2 ) > n)	x2 = n,a -= (n - x),x1 = x - a; 	} 	return  {x1,x2}; } int  main () 	scanf ("%d%d%d%d%d%d" ,&n,&m,&x,&y,&a,&b); 	int  g = __gcd(a,b);a /= g,b /= g; 	 	 	 	int  tmp = min (n/a,m/b); 	 	PII X = get (x,tmp * a,n); 	PII Y = get (y,tmp * b,m); 	 	printf ("%d %d %d %d\n" ,X.first,Y.first,X.second,Y.second); 	return  0 ; } 
 
怪物们又入侵了小镇!明日香邀请好友水母一起驾驶 EVA。
小镇上有 n n n n n n 1 1 1 n n n 
这里有 m m m i i i a i a_i a i  b i b_i b i  有向的 。也就是说,使用第 i i i b i b_i b i  a i a_i a i  a i < b i a_i < b_i a i  < b i  
驾驶 EVA 需要两人合作。然而,明日香和水母之前并没有一起接受过任何训练。
假设 EVA 目前在城市 u u u u u u v 1 v_1 v 1  v 2 v_2 v 2  v 1 = v 2 v_1 = v_2 v 1  = v 2  v 1 v_1 v 1  u u u 
EVA 目前可能在城市 u u u u ≠ n u \neq n u  = n u u u n n n 
每次选择道路时,水母都知道明日香会随机选择一条道路。现在,水母想知道,如果她最优化地选择道路,任务成功的最大概率是多少。
2 ≤ n ≤ 5000 2 \le n \le 5000 2 ≤ n ≤ 5 0 0 0 0 ≤ m ≤ min  ( 2 ⋅ 1 0 5 , n ( n − 1 ) 2 ) 0 \le m \le \min(2 \cdot 10^5,\frac{n(n-1)}{2}) 0 ≤ m ≤ min ( 2 ⋅ 1 0 5 , 2 n ( n − 1 )  ) 
一个显然的想法就是要预处理出来 f [ i ] f[i] f [ i ] i i i 
显然对于一条边我们越早选择其成功走过去的概率越大,所以若当前枚举到了点 i i i f f f 
所以我们现在其实就是要求求解:d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] i i i f f f j j j f f f 
首先无论我们最终选择了什么,按照上文的策略,我们此时选择的边必然是按 f f f 1 1 1 
所以要对 d p [ i ] [ 1 ] dp[i][1] d p [ i ] [ 1 ] 
对于其他的转移,就是考虑当前随机选择了哪一条边,然后算一下概率即可。
 点击查看代码  
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 72 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 5005 ;vector<int > G[N],IG[N]; int  n,deg[N];double  f[N],dp[N][N];bool  cmp (int  a,int  b) 	return  f[a] > f[b]; } void  get_ans () 	f[n] = 1 ; 	queue<int > q; 	for (int  i=1 ; i<=n; i++){ 		if (!deg[i])	q.push (i); 	} 	while (!q.empty ()){ 		int  now = q.front ();q.pop (); 		sort (G[now].begin (),G[now].end (),cmp); 		for (int  i=0 ; i<(int )G[now].size (); i++){ 			int  to = G[now][i]; 			f[now] += 1.0  * dp[G[now].size ()][i+1 ] * f[to]; 		} 		for (auto  to : IG[now]){ 			deg[to]--; 			if (!deg[to])	q.push (to); 		} 	} } int  main () 	int  T;scanf ("%d" ,&T); 	while (T--){ 		int  m;scanf ("%d%d" ,&n,&m); 		for (int  i=1 ; i<=m; i++){ 			int  from,to;scanf ("%d%d" ,&from,&to); 			G[from].push_back (to); 			IG[to].push_back (from);deg[from]++; 		} 		 		dp[1 ][1 ] = 1 ; 	 		for (int  i=2 ; i<=n; i++){ 			 			dp[i][1 ] += 1.0  / i + 1.0  * (i-1 ) / i * 0 ; 			for (int  j=2 ; j<=i; j++){ 				 				dp[i][j] += 1.0  / i * 0 ; 				 				dp[i][j] += 1.0  * (j-2 ) / i * dp[i-2 ][j-2 ]; 				 				dp[i][j] += 1.0  / i * 0 ; 				 				dp[i][j] += 1.0  * (i-j) / i * dp[i-2 ][j-1 ]; 			} 		} 		 		get_ans (); 		printf ("%.9f\n" ,f[1 ]); 		 		for (int  i=0 ; i<=n; i++){ 			f[i] = 0 ;deg[i] = 0 ;G[i].clear ();IG[i].clear (); 			for (int  j=0 ; j<=n; j++){ 				dp[i][j] = 0 ; 			} 		} 	} 	return  0 ; } 
 
维护一个字符串集合,支持三种操作:
加字符串 
删字符串 
查询集合中的所有字符串在给出的模板串中出现的次数 
 
操作数 m ≤ 3 × 1 0 5 m \leq 3 \times 10^5 m ≤ 3 × 1 0 5 ∑ ∣ s i ∣ ≤ 3 × 1 0 5 \sum |s_i| \leq 3\times 10^5 ∑ ∣ s i  ∣ ≤ 3 × 1 0 5 
本题强制在线,应该在每次输出后调用fflush(stdout)。你只有在输出上一个询问的答案后才能读入下一组询问。
这里写一种很让人震撼的做法。
考虑根号分治,先设置一个阈值 B B B B B B 
若现在有一个查询操作。
对于长度小于等于 B B B B B B O ( B ∣ S ∣ ) O(B|S|) O ( B ∣ S ∣ ) 
对于长度大于 B B B ∣ S ∣ B \frac{|S|}{B} B ∣ S ∣  O ( ∣ S ∣ 2 B ) O(\frac{|S|^2}{B}) O ( B ∣ S ∣ 2  ) 
若取 B = ∣ S ∣ B = \sqrt{|S|} B = ∣ S ∣  O ( ∣ S ∣ ∣ S ∣ ) O(|S|\sqrt{|S|}) O ( ∣ S ∣ ∣ S ∣  ) 
 点击查看代码  
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 72 73 74 75 76 77 78 #include <bits/stdc++.h>  using  namespace  std;const  int  N = 3e5 +5 ;struct  trie {	int  ch[N][27 ],val[N]; 	int  rt = 1 ,tot = 1 ; 	void  insert (string s,int  p)  		int  now = rt; 		for (int  i=0 ; i<(int )s.size (); i++){ 			if (!ch[now][s[i]-'a' ])	ch[now][s[i]-'a' ] = ++tot; 			now = ch[now][s[i]-'a' ]; 		} 		val[now] += p; 	} 	int  find (char  *s,int  len)  		int  now = rt,ans = val[now]; 		for (int  i=0 ; i<len; i++){ 			if (!ch[now][s[i]-'a' ])	break ; 			now = ch[now][s[i]-'a' ]; 			ans += val[now]; 		} 		return  ans; 	} }Trie; int  nxt[N],sz;char  s[N];string v[N]; int  g[N];int  search (string S,string T) 	S = 'a'  + S;   	T = 'a'  + T;   	 	int  n = S.size (),m = T.size (); 	 	int  ans = 0 ; 	 	nxt[1 ] = 0 ; 	for (int  i=2 ,j=0 ; i<n; i++){ 		while (j && S[i] != S[j+1 ])	j = nxt[j]; 		if (S[j+1 ] == S[i])	++j; 		nxt[i] = j; 	} 	 	for (int  i=1 ,j=0 ; i<m; i++){ 		while (j && S[j+1 ] != T[i])	j = nxt[j]; 		if (S[j+1 ] == T[i])	++j; 		if (j == n-1 ){ 			j = nxt[j]; 			++ans; 		} 	} 	return  ans; } int  main () 	int  m;scanf ("%d" ,&m); 	while (m--){ 		string S = "" ; 		int  t;scanf ("%d" ,&t);cin>>S; 		int  len = S.size (); 		for (int  i=0 ; i<len; i++)	s[i+1 ] = S[i]; 		if (t == 1  || t == 2 ){ 			if (len <= 1000 )	Trie.insert (S,t == 1  ? 1  : -1 ); 			else 	v[++sz] = S,g[sz] = (t == 1  ? 1  : -1 ); 		} 		else { 			int  ans = 0 ; 			for (int  i=1 ; i<=len; i++)	ans += Trie.find (s+i,len-i+1 ); 			for (int  i=1 ; i<=sz; i++){ 				if (v[i].size () <= S.size ()) 					ans += g[i] * search (v[i],S); 			} 			printf ("%d\n" ,ans),fflush (stdout); 		} 	} 	return  0 ; }  
 
给定一颗 n n n a i ∈ [ 1 , m ] a_i \in [1,m] a i  ∈ [ 1 , m ] i i i j j j s i , j s_{i,j} s i , j  
注意给定树相当于一棵完美二叉树,按顺序编号,从某一个结点开始其余的节点全部扔掉后形成的二叉树。
求所有树(m n m^n m n ∑ i = 1 n ∑ j = i n s i , j \sum_{i=1}^n \sum_{j=i}^n s_{i,j} ∑ i = 1 n  ∑ j = i n  s i , j  998244353 998244353 9 9 8 2 4 4 3 5 3 
1 ≤ n ≤ 1 0 18 , 1 ≤ ∑ m ≤ 1 0 5 1 \le n \le 10^{18},1 \le \sum m \le 10^5 1 ≤ n ≤ 1 0 1 8 , 1 ≤ ∑ m ≤ 1 0 5 t ≤ 200 t \le 200 t ≤ 2 0 0 
既然是对于所有的路径计数,一个显然的想法就是枚举 L C A LCA L C A 
这样对于一条长度为 l e n len l e n m x mx m x m x × [ m x l e n − ( m x − 1 ) l e n ] × m n − l e n mx \times [mx^{len} - (mx-1)^{len}] \times m^{n-len} m x × [ m x l e n − ( m x − 1 ) l e n ] × m n − l e n 
所以其实我们只要知道了长度为 l e n len l e n 
但是注意到如果这是一棵完美二叉树我们是会算的,所以就考虑能不能把这个看上去就很有性质的二叉树转化为完美二叉树。
注意到其实节点 n n n 1 1 1 n n n 
所以剩余子树内部的贡献我们就可以直接通过预处理完美二叉树的对应的贡献算出,而剩余子树之间必然会以 1 1 1 n n n L C A LCA L C A 
预处理完美二叉树的贡献就是考虑一棵深度为 d e p dep d e p d e p − 1 dep-1 d e p − 1 L C A LCA L C A 
注意的一点就是我们在知道了路径条数之后统计答案的时候,不能直接使用快速幂计算,因为原本的复杂度就有点爆炸,现在又多了一个 O ( log  n ) O(\log n) O ( log  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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <bits/stdc++.h>  #define  int long long using  namespace  std;const  int  N = 1000 +5 ;const  int  MOD = 998244353 ;int  f[N][N],pw2[100005 ][300 ],pw[N],tmp[N],ans[N],pre[N];void  add (int  &a,int  b) 	a = (a + b) % MOD; } int  get (int  a,int  b) 	int  tot = 0 ; 	while (a <= b){ 		a <<= 1 ; 		++tot; 	} 	return  tot; } int  get2 (int  x) 	if (x == 0 ) return  1 ; 	return  pw[x-1 ]; } int  power (int  a,int  b) 	int  res = 1 ; 	while (b){ 		if (b & 1 )	res = res * a % MOD; 		a = a * a % MOD; 		b >>= 1 ; 	} 	return  res; } signed  main () 	pw[0 ] = 1 ; 	for (int  i=1 ; i<=100 ; i++)	pw[i] = pw[i-1 ] * 2  % MOD; 	 	for (int  i=1 ; i<=100000 ; i++){ 		pw2[i][0 ] = 1 ; 		for (int  j=1 ; j<=200 ; j++)	pw2[i][j] = pw2[i][j-1 ] * i% MOD; 	} 	 	f[0 ][0 ] = 1 ; 	for (int  i=1 ; i<=100 ; i++){ 		f[i][0 ] = 1 ; 		for (int  j=0 ; j<i; j++){    			for (int  k=0 ; k<i; k++){ 				add (f[i][j+k+1 ],get2 (j) * get2 (k) % MOD); 			} 		} 		for (int  j=1 ; j<=2 *i; j++)	add (f[i][j],2  * f[i-1 ][j] % MOD); 	} 	 	int  T;scanf ("%lld" ,&T); 	while (T--){ 		int  n,m;scanf ("%lld%lld" ,&n,&m); 		int  now = n,len = 2 ; 		pre[0 ] = 1 ,pre[1 ] = 1 ; 		ans[1 ] = 1 ; 		while (now > 1 ){ 			++len; 			 			int  bro = now ^ 1 ;  			int  dep = get (bro,n);    			for (int  l=0 ; l<=len; l++){ 				if (!pre[l])	break ; 				for (int  r=0 ; r<=dep; r++){ 					add (ans[l+r+1 ],pre[l] * get2 (r)); 				} 			} 			for (int  i=0 ; i<=2 *dep; i++)	add (ans[i],f[dep][i]); 			 			for (int  l=0 ; l<=len; l++)	add (tmp[l+1 ],pre[l]); 			for (int  r=0 ; r<=dep; r++){ 				int  t = 0 ; 				if (r == 0 )	t = 1 ; 				else 	t = pw[r - 1 ]; 				add (tmp[r+1 ],t); 			} 			tmp[1 ] = 1 ;tmp[0 ] = 1 ; 			for (int  i=0 ; i<=len; i++)	pre[i] = tmp[i],tmp[i] = 0 ; 			 			now >>= 1 ; 		} 		int  res = 0 ; 		int  tt = power (m,n),inv = power (m,MOD-2 ); 		for (int  i=1 ; i<=200 ; i++){ 			tt = tt * inv % MOD; 			for (int  mx=1 ; mx<=m; mx++){ 				if (ans[i]){ 					add (res,mx * ans[i] %MOD * (pw2[mx][i] - pw2[mx-1 ][i]) %MOD * tt % MOD); 				} 			} 		} 		for (int  i=0 ; i<=200 ; i++) ans[i] = pre[i] = tmp[i] = 0 ; 		printf ("%lld\n" ,(res + MOD) % MOD); 	} 	return  0 ; }