码迷,mamicode.com
首页 > 其他好文 > 详细

ARC106 选做

时间:2020-10-26 11:05:55      阅读:24      评论:0      收藏:0      [点我收藏+]

标签:例子   gis   +=   return   cal   begin   pac   方法   namespace   

ARC106 选做

ARC106D [* easy]

给定长度为 \(N\) 的数列 \(A\),对于 \(X=1,2...K\) 计算:

\[\sum_i\sum_j (A_i+A_j)^X[i<j] \]

\(N\le 2\times 10^5,K\le 300\)

Solution

考虑二项式定理:

\[\begin{aligned} &\sum_k \binom{X}{k}\sum_i\sum_j A_i^kA_j^{X-k}[i<j] \\&\sum_k\binom{X}{k} \frac{1}{2}\bigg(\Big(\sum_i A_i^k\Big)\Big(\sum_j A_j^{X-k}\Big)-\sum_i A_i^kA_i^{X-k}\bigg) \end{aligned}\]

预处理 \(\sum A_i^k\),设为 \(S_k\),答案可以表示为:

\[\begin{aligned} &\frac{1}{2}\sum_k \binom{X}{k}\bigg(S_kS_{X-k}-\sum_i A_i^kA_i^{X-k}\bigg) \\&\frac{1}{2}\bigg(\sum_k S_kS_{X-k}\binom{X}{k}-\sum_i\sum_k A_i^kA_i^{X-k}\binom{X}{k}\bigg) \\&\frac{1}{2}\bigg(\sum_k S_kS_{X-k}\binom{X}{k}-\sum_i (2A_i)^X\bigg) \end{aligned}\]

\(\mathcal O(NK)\) 的预处理,然后 \(\mathcal O(K^2)\) 的 count 即可,复杂度 \(\mathcal O(NK+K^2)\)

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < ‘0‘ || cc > ‘9‘ ) {  if( cc == ‘-‘ ) flus = - flus ; cc = getchar() ; }
	while( cc >= ‘0‘ && cc <= ‘9‘ )  cn = cn * 10 + cc - ‘0‘, cc = getchar() ;
	return cn * flus ;
}
const int P = 998244353 ; 
const int N = 2e5 + 5 ; 
const int M = 300 + 5 ; 
int fpow(int x, int k) {
	int ans = 1, base = x ;
	while(k) {
		if(k & 1) ans = 1ll * ans * base % P ;
		base = 1ll * base * base % P, k >>= 1 ;
	} return ans ;
}
int n, m, C[M][M], a[N], S[M], f[M], I ; 
signed main()
{
	n = gi(), m = gi() ; 
	rep( i, 1, n ) a[i] = gi() ; 
	C[0][0] = 1 ; 
	rep( i, 1, m ) rep( j, 0, i ) 
		C[i][j] = (!j) ? 1 : (C[i - 1][j - 1] + C[i - 1][j]) % P ;
	S[0] = n, f[0] = n ; 
	rep( i, 1, n ) {
		int z = 1, t = 1 ;
		rep( j, 1, m ) 
			z = z * a[i] % P, t = t * (a[i]) * 2 % P, 
			S[j] = (S[j] + z) % P, f[j] = (f[j] + t) % P ; 
	}
	I = (P + 1) / 2 ;
	rep( i, 1, m ) {
		int Ans = 0 ; 
		rep( j, 0, i ) Ans = (Ans + C[i][j] * S[j] % P * S[i - j] % P) % P ;
		Ans = (Ans - f[i] + P) % P ;
		cout << Ans * I % P << endl ; 
	}
	return 0 ;
}

ARC106E [* easy]

\(n\) 个工人,第 \(i\) 个人第 \([1,A_i]\) 天工作,第 \([A_i+1,2A_i]\) 休息,然后 \([2A_i+1,3A_i]\) 工作,依次类推。

你需要给这些工人发工资,使得所有工人都至少领到了 \(K\) 个硬币,规则是你每天可以选择一个在工作的工人发一枚硬币。

求最少多少天可以发完。

\(N\le 18,K\le 10^5,A_i\le 10^5\)

Solution

我们发现答案的下界是 \(NK\)

我们发现我们存在一种 \(2NK\) 级的策略,就是一个人一个人的发完,显然这个策略的答案是 \(2NK\) 这个级别的。

当然,显然我们可以更优的给硬币,我们可以猜测答案的上界是 \(2NK\)

同时不难给出 \(2NK\) 的例子,即所有 \(A_i\) 相同。

考虑二分答案,然后这个模型非常像网络流问题,考虑暴力网络流建模:

  • 从源点 \(S\) 连向每天 \(i\) 一点流量。
  • 从每个人 \(j\to T\)\(K\) 点流量。
  • 每天向这一天在工作的每个人连向 \(\infty\) 的流量。

此时,如果这张图流量为 \(NK\) 就说明合法。

不难注意到有不少天 \(i\)\(j\) 的连边是相似的,我们可以将他们压缩到一起,因为本质不同的连边只有 \(2^n\) 种(即这一天连向所有人的可能的情况)

此时这张图为二分图,暴力 dinic 的复杂度为 \(\mathcal O(M\sqrt{N})\) 近似于 \(n\cdot 2^{\frac{3}{2}n}\) 这个级别。

我们肯定不是暴力网络流,考虑最大流等于最小割,我们考虑这张图的最小割。

我们发现这张二分图的 \(T\) 边的节点数只有 \(N\) 个,而 \(S\) 边有 \(2^N\) 个,同时 \(S\) 边的每个节点均为一个状态,它只会向 \(T\) 边此位为 \(1\) 的点连边。

考虑枚举 \(T\) 边的割边情况,此时另一边不用被割去的边仅有此边对应的状态为它的子集的情况。那么可以使用高维前缀和/FMT来进行预处理,然后取 \(\min\) 来得到最小割即可,如果最小割为 \(NK\) 就说明合法。

复杂度为 \(\mathcal O(N^2K+N2^N\log (NK))\)

其中 \(N^2K\) 为预处理每个时间点对应的状态的复杂度。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define Rep( i, s, t ) for( register int i = (s); i < (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < ‘0‘ || cc > ‘9‘ ) {  if( cc == ‘-‘ ) flus = - flus ; cc = getchar() ; }
	while( cc >= ‘0‘ && cc <= ‘9‘ )  cn = cn * 10 + cc - ‘0‘, cc = getchar() ;
	return cn * flus ;
}
const int N = (1 << 18) + 5 ; 
const int M = 5e6 + 5 ; 
int n, K, A[20], lim, limit, sta[M] ; 
int f[N] ; 
int bit(int x) {
	return __builtin_popcount(x) ; 
}
bool check(int x) {
	limit = (1 << n) - 1 ; 
	rep( i, 0, limit ) f[i] = 0 ;
	rep( i, 1, x ) ++ f[sta[i]] ; 
	int ans = n * K ; 
	for(re int k = 1; k <= limit; k <<= 1)
	rep( i, 0, limit ) if(i & k) f[i] += f[i ^ k] ; 
	for(re int i = 0; i <= limit; ++ i) 
		ans = min( bit(i) * K + f[limit] - f[i], ans ) ; 
	return (ans == n * K) ; 
}
signed main()
{
	n = gi(), K = gi(), lim = 2 * n * K ; 
	Rep( i, 0, n ) A[i] = gi() ; 
	rep( i, 1, lim ) Rep( j, 0, n ) 
		if(!(((i - 1) / A[j]) & 1)) sta[i] |= (1 << j) ;  
	int l = 0, r = lim, ans = 0 ;
	while( l <= r ) {
		int mid = (l + r) >> 1 ;
		if(check(mid)) ans = mid, r = mid - 1 ;
		else l = mid + 1 ; 
	}
	cout << ans << endl ; 
	return 0 ;
}

ARC106F [* easy]

给定 \(n\) 个机器人,第 \(i\) 个机器人有 \(d_i\) 个接口。

你需要将机器人连接成树,方法为分别选定两个不同的机器人的一个未被选择的接口,然后连接这两个机器人。

求本质不同的树的数量,其中连接的接口不同视为树不同。

答案对 \(998244353\) 取模。

\(N\le 2\times 10^5,d_i<998244353\)

Solution

考虑这 \(n\) 个机器人生成的树,假设此树上机器人 \(i\) 的度数为 \(c_i\),那么贡献为 \(d_i^{\underline{c_i}}\)(考虑给边标号,然后再任意排布)

现在从 prufer 序列的角度来考虑答案,由于每个点的度数都是出现次数 \(+1\),方便起见我们给答案乘以 \(\prod d_i\),然后给 \(d_i-1\),现在需要统计长度为 \(N-2\) 的序列的所有贡献和。

使用 EGF 刻画答案,我们不难发现:

\[\prod(\sum_k \frac{x^kd_i^{\underline{k}}}{k!})[x^{N-2}]\times (N-2)! \]

即为答案。

考虑前半部分:

\[\prod\bigg(\sum_k x^k\binom{d_i}{k}\bigg)=(1+x)^{\sum d_i} \]

\((1+x)^{k}[x^{z}]=\binom{k}{z}\)

\(D=\sum d_i,N\leftarrow N-2,M=\sum d_i\),我们有答案即为:

\[\binom{D}{N}\times N!\times M=D^{\underline{N}}\times M \]

这样就避免了分母为 \(0\) 的问题了。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < ‘0‘ || cc > ‘9‘ ) {  if( cc == ‘-‘ ) flus = - flus ; cc = getchar() ; }
	while( cc >= ‘0‘ && cc <= ‘9‘ )  cn = cn * 10 + cc - ‘0‘, cc = getchar() ;
	return cn * flus ;
}
const int P = 998244353 ;  
int fpow(int x, int k) {
	int ans = 1, base = x ;
	while(k) {
		if(k & 1) ans = 1ll * ans * base % P ;
		base = 1ll * base * base % P, k >>= 1 ;
	} return ans ;
}
int n, D, M ; 
signed main()
{
	n = gi() ; int x ; M = 1 ; 
	rep( i, 1, n ) x = gi(), D = (D + x - 1) % P, M = M * x % P ; 
	n -= 2 ; 
	rep( i, 1, n ) M = M * (D - i + 1) % P ; 
	cout << M << endl ;  
	return 0 ;
}

ARC106 选做

标签:例子   gis   +=   return   cal   begin   pac   方法   namespace   

原文地址:https://www.cnblogs.com/Soulist/p/13872450.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!