1. 程式人生 > 實用技巧 >ARC104 選做

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 ;
}