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)
这样部分元素贡献为正,部分元素贡献为负,即
即:
注意到每个元素的上界都是相同的,换而言之元素本身没有区别,于是两边只关乎元素的数量,设 (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) 为结尾的贡献和,那么转移形如:
注意到需要使用的组合数来自于本质不同的差值以及他们的偏移量,至多为 (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 ;
}