zoukankan      html  css  js  c++  java
  • ARC104 选做

    ARC104 选做

    ARC104C

    给定长度为 (2N) 的序列,给出 (N) 个区间,区间的端点互不相同。

    如果两个区间相交,那么他们长度必须相同,我们给出部分区间的 (l_i),部分区间的 (r_i),部分区间的 (l_i)(r_i),判定能否构造一组合法的解。

    (Nle 100)

    Solution

    考虑最后的答案,假设一些区间相交,那么 (l)(r) 一定会规矩的排布下去。然后这些排布的最小的 (l) 和最大的 (r) 可以视为构成区间 ([L,R])

    所以考虑设 (f_i) 表示 ([1,i]) 能否被合法处理掉,转移枚举 (j),然后check ([j+1,i]),check 的话注意到步长 (L) 一定是长度 /2, 然后直接以 (L) 为步长看一下合不合法即可。

    复杂度,挺低的吧,(mathcal O(N^3))

    Code: 咕咕咕


    ARC104D

    给定 (N,K,M),对于 (xin [1,N]),求长度为 (N) 整数序列 ({a}) 的数量,满足:

    • (forall iin [1, N],0le a_ile K)

    • [frac{sum a_i imes i}{sum a_i}=x ]

    求方案数,答案对 (M) 取模。

    (N,Kle 100,Min [10^8,10^9+9])(M) 是一个质数。

    Solution

    考虑变换式子变成 (sum a_i(i-x)=0)

    这样部分元素贡献为正,部分元素贡献为负,即

    [sum a_i imes (i-x)[ige x]=sum_{}a_i imes (x-i)[ile x] ]

    即:

    [sum a_{x+t} imes t=sum a_{x-t} imes t ]

    注意到每个元素的上界都是相同的,换而言之元素本身没有区别,于是两边只关乎元素的数量,设 (f_{i,j}) 为当前有 (i) 个元素,权值和为 (j) 的方案数,转移形如 (f_{i,j}=sum_{k=1}^K f_{i-1,j-ik}),在模 (i) 意义下分段前缀和优化一下即可。复杂度 (mathcal O(N^3K))

    计算方案数则直接枚举 (x) 然后左右合并即可,复杂度 (mathcal O(N^3K))

    (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 N = 100 + 5 ; 
    const int M = 6e5 + 5 ; 
    int n, m, P, lim, Ans[N], dp[N][M], sum[N][M] ; 
    signed main()
    {
    	n = gi(), m = gi(), P = gi(), lim = n * (n + 2) / 2 * m, dp[0][0] = 1 ;
    	rep( i, 1, n ) {
    		rep( j, 0, i - 1 ) sum[i][j] = dp[i - 1][j] ; 
    		rep( j, i, lim ) sum[i][j] = (sum[i][j - i] + dp[i - 1][j]) % P ;
    		rep( j, 0, (m + 1) * i - 1 ) dp[i][j] = sum[i][j] ;
    		rep( j, i * (m + 1), lim ) dp[i][j] = (sum[i][j] - sum[i][j - i * (m + 1)] + P) % P ; 
    	}
    	rep( i, 1, n ) rep( j, 0, lim ) 
    		Ans[i] = (Ans[i] + dp[i - 1][j] * dp[n - i][j]) % P ; 
    	rep( i, 1, n ) printf("%lld
    ", (Ans[i] * (m + 1) % P + P - 1) % P ) ; 
    	return 0 ;
    }
    

    ARC104E

    给定长度为 (N) 的序列 (A),有一个整数序列 ({a}),其中 (a_i)([1,A_i]) 中的一个随机整数,求 (a_i) 的最长严格递增子序列的期望。答案对 (10^9+7) 取模。

    (1le Nle 6,A_iin [1,10^9])

    Solution

    考虑 (mathcal O(N!)) 的枚举所有元素的大小关系,此时我们规定大小关系是双关键字的,即优先权值从小到大,权值相同那么按照下标从小到大。

    对于一种大小关系,我们可以得到其对于答案的贡献,只需要计算满足其的序列的数量即可。

    这样假设 (p_i>p_{i+1}),此时我们有 (a_{p_i}le a_{p_{i+1}}),否则为 (a_{p_i}<a_{p_{i+1}})

    考虑 (a_{p_i}<a_{p_{i+1}}) 的限制比较鬼畜,我们索性直接给后缀的限制上界集体减去 (1),然后这样就全体都是 (a_{p_i}le a_{p_{i+1}}) 了。

    然后可以轻易的得到一个 (mathcal O(值域)) 的 dp

    然而由于值域太大,(N) 很小,所以考虑将值域进行分段,然后我们逐段 Dp,不难论证每一段的 dp 值是关于权值数量的 (N) 次多项式,这样只需要知道 (mathcal O(N)) 个点值就可以计算答案,最后拉格朗日插值一下即可,复杂度 (mathcal O(N^3cdot N!))

    有没有更简单的做法呢?

    考虑我们的限制形如 (a_ile A_i),求单调递不降的序列的方案数,这玩意儿没有限制下界,那当然有更 easy 的算法。

    我们先把 (A_i) 后缀取 (min)

    我们考虑容斥,我们枚举那些位置超出了 (A_i) 即至少是 (A_i+1),这样的一个方案对答案的贡献是 ((-1)^k),考虑使用 Dp 来统计,设 (f_i) 表示所有子集中以 (i) 为结尾的贡献和,那么转移形如:

    [f_i=-sum_{j<i}f_jinom{A_i-A_j+(i-j)}{i-j} ]

    注意到需要使用的组合数来自于本质不同的差值以及他们的偏移量,至多为 (N),本质不同的差值仅有 (mathcal O(N^2)) 对,算上偏移量,我们需要计算的组合数均形如 (inom{x+i-1}{i}),这些 (x) 只有 (mathcal O(N^3)) 级,我们只需要预处理 (mathcal O(N^4)) 级别的组合数,这样复杂度即为 (mathcal O(N^2cdot N!+N^4))

    预处理部分复杂度大概是 (mathcal O(N^4sim N^5)) 的样子?

    不过我懒,就没写了。

    (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 N = 10 ; 
    const int P = 1e9 + 7 ; 
    int n, Ans, Num, A[N], h[N], f[N], g[N], Id[N], fac[N], inv[N] ;
    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 C(int x, int y) {
    	int ans = 1 ; 
    	rep( i, 1, y ) ans = ans * (x - i + 1) % P ; 
    	return ans * inv[y] % P ; 
    }
    signed main()
    {
    	n = gi(), fac[0] = inv[0] = 1 ; 
    	rep( i, 1, n ) fac[i] = fac[i - 1] * i % P, inv[i] = fpow( fac[i], P - 2) ;
    	rep( i, 1, n ) A[i] = gi() - 1, Id[i] = i ; 
    	do {
    		rep( i, 1, n ) h[i] = A[Id[i]] ;
    		rep( i, 1, n - 1 ) if( Id[i] < Id[i + 1] ) 
    			rep( j, i + 1, n ) -- h[j] ; 
    		drep( i, 1, n - 1 ) h[i] = min( h[i], h[i + 1] ) ; 
    		f[0] = 1, h[0] = -1, Ans = C(h[n] + n, n) ;	
    		rep( i, 1, n ) {
    			f[i] = P - C(h[i] + i, i - 1) ; 
    			for(re int j = 1; j < i; ++ j) 
    			f[i] = (f[i] - f[j] * C(h[i] - h[j] + i - j, i - j) % P + P) % P ; 
    			Ans = (Ans + f[i] * C(h[n] - h[i] + (n - i), n - i + 1)) % P ;
    		}
    		int d = 0 ; 
    		g[0] = 0, memset( g, 0, sizeof(g) ) ; 
    		rep( i, 1, n ) rep( j, 0, i - 1 ) 
    		if( Id[j] < Id[i] ) g[i] = max( g[i], g[j] + 1 ), d = max(d, g[i]) ; 
    		Num = (Num + Ans * d) % P ; 	
    	} while(next_permutation(Id + 1, Id + n + 1)) ;
    	int iv = 1 ; 
    	rep( i, 1, n ) iv = (iv * (A[i] + 1)) % P ;
    	iv = fpow(iv, P - 2) ; 
    	cout << Num * iv % P << endl ;
    	return 0 ;
    }
    

    ARC104F

    给定长度为 (N) 的序列 (X),令 (H_i)([1,X_i]) 中的一个随机整数。

    定义 (P_i) 为:

    • 如果存在 (j>i)(H_j>H_i) 那么 (P_i=max j)
    • 否则 (P_i=-1)

    求所有可能的 (H) 序列生成的 (P) 序列的数量,答案对 (10^9+7) 取模。

    (Nle 100,X_ile 10^5)

    Solution

    考虑已经有一个 (P) 序列,如何 check。

    我们发现如果存在 (H) 序列能够生成他,那么这个 (H) 序列的元素上界是 (N)

    考虑最后一个 (-1) 位于 (x),他将序列分成两个部分,后面部分的权值均小于其,且 (P_i) 不能跨过其,所以可以当作子区间处理。此时 (H_x) 为右区间内 (H) 的最大值 (+1)

    然后 ([1,x-1]) 也可以当作子区间处理,区间最大值也是 (H_x) 的一个可能的取值。

    这样只需要做一遍 (max) 卷积即可,状态数 (mathcal O(N^3)),转移数 (mathcal O(N^4))

    u1s1,这个 E + F 的给人的感觉和 NOI2019 机器人很相似。

    F 的 dp trick 感觉和机器人的部分分挺像的,E 的优化 trick 感觉完全没有机器人高明。

    (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 N = 100 + 5 ; 
    const int P = 1e9 + 7 ; 
    int n, X[N], dp[N][N][N], Sum[N][N][N] ; 
    void inc(int &x, int y) {
    	x += y, x %= P ; 
    }
    signed main()
    {
    	n = gi() ; srand(time(NULL)) ; 
    	rep( i, 1, n ) X[i] = gi(), X[i] = min( X[i] - 1, n ) ; 
    	rep( i, 1, n ) {
    		dp[i][i][0] = 1 ; 
    		rep( j, 0, n ) Sum[i][i][j] = 1 ; 
    	}
    	for(re int len = 1; len <= n; ++ len) 
    	for(re int l = 1; l <= n; ++ l) {
    		int r = l + len ; if( r > n ) break ; 
    		for(re int x = l; x <= r; ++ x) {
    			if( x == l ) 
    				rep( j, 1, X[x] ) inc(dp[l][r][j], dp[x + 1][r][j - 1]) ;
    			else if( x == r ) 
    				rep( j, 0, X[x] ) inc(dp[l][r][j], dp[l][x - 1][j]) ; 
    			else {
    				rep( j, 1, X[x] ) 
    					inc(dp[l][r][j], dp[l][x - 1][j] * Sum[x + 1][r][j - 1] % P),
    					inc(dp[l][r][j], Sum[l][x - 1][j - 1] * dp[x + 1][r][j - 1] % P) ;
    			}
    		}
    		Sum[l][r][0] = dp[l][r][0] ; 
    		rep( j, 1, n ) Sum[l][r][j] = (Sum[l][r][j - 1] + dp[l][r][j]) % P ; 
    	}
    	cout << Sum[1][n][n] % P << endl ; 
    	return 0 ;
    }
    
  • 相关阅读:
    JS获取四位年份和2位年份
    notebook 快捷键
    发表文章不需要版面费的期刊
    命题演算、集合论和布尔代数之间的关系是什么?
    炒作套路
    如何理解佛经中“黄叶止啼”的故事
    期货之为什么要注册仓单!逼空是什么鬼!
    反证法与归谬法的区别
    感恩的含义!告诉你什么是感?什么是恩?人生必读!
    悖论的本质
  • 原文地址:https://www.cnblogs.com/Soulist/p/13778611.html
Copyright © 2011-2022 走看看