• 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_@_H_8Z`R_VLOSLQN.png

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

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

    #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) imes (f(v,0) + f(v,1)) + f(u,0) imes f(v,1) $

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

    #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 $ 。

    #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;
    }
    
  • 相关阅读:
    汇编指令记录
    nginx源码剖析(3)nginx中的内存池
    STL中的vector
    Direct3D学习笔记
    委托的作用
    vs2010 快捷键大全
    Web Service学习笔记:什么是Web Service
    [Serializable]在C#中的作用NET 中的对象序列化
    .NET中反射机制的使用与分析
    什么时候用WebService
  • 原文地址:https://www.cnblogs.com/yijan/p/comp1107.html
Copyright © 2020-2023  润新知