11.7 模拟赛

11.7 模拟赛

模拟赛鸽了几天没写题解了。。

T1 煎蛋的疑惑

考虑每一个左括号表示+1,一个右括号-1,那么画出来的图是这样的

R15~YU___31CH_I1JVFTJ99.png
考虑把沿着$x = -m$翻折一下,也就是终点变成了$(2n , -2m)$

然后假设一次上升为 +1 一次下降为 -1 那么我们相当于是要钦定$n - m$个位置上升,$n + m$个位置下降。那么如果我们钦定$n + m$个位置下降是不是就做完了呢?并不是,然而$n + m$个位置下降仍然会导致一种非法情况

VN_@`0E`_H_8Z`R_VLOSLQN.png

当然这种情况告诉我们,最低点其实是 小于$m$ 而不是等于$m$,当前缀和减一下就好了。

答案$C_{2n}^{n+m} - C_{2n}^{n+m+1}$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
#define MAXN 1000006
#define int long long
#define P 998244353
int n , m;
int J[MAXN << 1];
int power( int a , int x ) {
int ans = 1 , cur = a % P;
while( x ) {
if( x & 1 ) ans *= cur , ans %= P;
cur *= cur , cur %= P , x >>= 1;
}
return ans;
}
int C( int a , int b ) {
return J[a] * power( J[b] * J[a - b] % P , P - 2 ) % P;
}
signed main() {
cin >> n >> m;
J[0] = 1; for( int i = 1 ; i < MAXN * 2 ; ++ i ) J[i] = J[i - 1] * i % P;
cout << ( C( 2 * n , n + m ) - C( 2 * n , n + m + 1 ) + P ) % P << endl;
}

T2 蘑菇

考虑“每个联通块大小积”的组合意义,就是把再每个连通块中选一个点方案数之和。于是我们可以当作 这个题 的弱化版了。

就是设$f(x,0)$表示以$x$为根的子树中$x$所在的连通块中没有选择点的方案数,$f(x , 1)$表示$x$为根的子树中$x$所在的连通块中有选择点的方案数。

考虑转移,$f( u , 0 ) = \prod ( f(v , 1) + f(v , 0) )$因为如果子树中已有 1 我们可以选择断开这条边,如果子树中没有 1 我们必须选择连上这条边这个点的选择方法有这些,可以加法原理加起来,再根据乘法原理相乘。

$f(u,1) = f(u,1) \times (f(v,0) + f(v,1)) + f(u,0) \times f(v,1)$

考虑这个点之前没有被选过,那么必须儿子是被选择过且连接这条边。 如果这个点之前被选择过了,儿子可以选择可以不选择,选择过断开这个边就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
#define MAXN 1000006
#define P 998244353
#define pb push_back
typedef long long ll;
int n;
ll w = ( P + 1 ) / 2 , W = 1;
ll dp[MAXN][2];
int read( ) {
char ch = ' '; int ret = 0;
while( ch > '9' || ch < '0' ) ch = getchar();
while( ch <= '9' && ch >= '0' ) ret *= 10 , ret += ch - '0' , ch = getchar();
return ret;
}
int head[MAXN] , to[MAXN << 1] , nex[MAXN << 1] , ecn;
void ade( int u , int v ) {
to[++ ecn] = v , nex[ecn] = head[u] , head[u] = ecn;
to[++ ecn] = u , nex[ecn] = head[v] , head[v] = ecn;
}
void dfs( int u , int fa ) {
dp[u][1] = dp[u][0] = 1;
for( int i = head[u] ; i ; i = nex[i] ) {
int v = to[i];
if( v == fa ) continue;
dfs( v , u );
dp[u][1] = 1LL * ( 1LL * dp[u][0] * dp[v][1] % P + 1LL * dp[u][1] * ( dp[v][0] + dp[v][1] ) % P ) % P;
dp[u][0] *= 1LL * dp[v][0] + dp[v][1] , dp[u][0] %= P;
}
}
signed main() {
cin >> n;
for( int i = 1 , u , v ; i < n ; ++ i ) u = read() , v = read( ) , ade( u , v ) , W *= w , W %= P;
dfs( 1 , 1 );
cout << 1LL * dp[1][1] * W % P << endl;
}

T3 墙

考虑$O(n^2)$空间就比较简单,用$dp[i][j]$表示当前在$i$上一个位置在$j$的方案数,其中$a_i < a_j$则表示当前在下降,否则表示上前在上升。

但是这题 卡 空 间 ….

考虑$up(i)$表示当前的为$i$,从这里开始先向下按照一下一上移动,最后在上升的方案数,设$dw(i)$表示当前值为$i$从这里开始向上一下一上移动,最后在上升的方案数。最后我们把所有$up(i)$加起来,但是这样只算完了一半,剩下一半可以一样地计算了。

考虑怎么转移$up( i )$,首先从大到小枚举$i$,然后从小到大枚举$j$然后分类

  • 如果$j$的位置在$i$的后面,可以直接用$j$来更新$i$
  • 否则,可以直接用$i$转移到$j$

为什么这样计算是正确的呢?考虑什么时候$i$会更新之前的$j$,如果$i$会更新之前的$j$,说明所有更新$i$的点都比$j$小,因为只有比$j$小的点此时更新了$j$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 6006
int n , P , res = 0;
int A[MAXN] , pos[MAXN];
int up[MAXN] , dw[MAXN];

int main() {
cin >> n >> P;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&A[i]) , pos[A[i]] = i , up[i] = 1 , dw[i] = 0;
for( int i = n ; i >= 1 ; -- i )
for( int j = 1 ; j < i ; ++ j )
if( pos[j] > pos[i] ) up[i] += dw[j] , up[i] %= P;
else dw[j] += up[i] , dw[j] %= P;
for( int i = 1 ; i <= n ; ++ i ) res += up[i] , res %= P , up[i] = 1 , dw[i] = 0;
for( int i = 1 ; i <= n ; ++ i )
for( int j = n ; j > i ; -- j )
if( pos[j] > pos[i] ) up[i] += dw[j] , up[i] %= P;
else dw[j] += up[i] , dw[j] %= P;
for( int i = 1 ; i <= n ; ++ i ) res += up[i] , res %= P;
cout << ( ( res - n ) % P + P ) % P << endl;
}
文章作者: yijan
文章链接: https://yijan.co/old18/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog