ARC104 选做

ARC104 选做

ARC104C

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

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

\(N\le 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\),对于 \(x\in [1,N]\),求长度为 \(N\) 整数序列 \(\{a\}\) 的数量,满足:

  • \(\forall i\in [1, N],0\le a_i\le K\)

  • \[\frac{\sum a_i\times i}{\sum a_i}=x \]

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

\(N,K\le 100,M\in [10^8,10^9+9]\)\(M\) 是一个质数。

Solution

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

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

\[\sum a_i\times (i-x)[i\ge x]=\sum_{}a_i\times (x-i)[i\le x] \]

即:

\[\sum a_{x+t}\times t=\sum a_{x-t}\times 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\n", (Ans[i] * (m + 1) % P + P - 1) % P ) ; 
	return 0 ;
}

ARC104E

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

\(1\le N\le 6,A_i\in [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^3\cdot N!)\)

有没有更简单的做法呢?

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

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

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

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

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

预处理部分复杂度大概是 \(\mathcal O(N^4\sim 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\) 取模。

\(N\le 100,X_i\le 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 ;
}
posted @ 2020-10-07 19:39  Soulist  阅读(334)  评论(0编辑  收藏  举报