• PKUSC2018 最大前缀和


    最大前缀和

    小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。

    但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。

    小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 (n!) 后对 (998244353) 取模的值,显然这是个整数。

    注:最大前缀和的定义:(forall i in [1,n])(sum_{j=1}^{i}a_j)的最大值。

    对于(100\%)的数据,满足(1leq nleq 20)(sum_{i=1}^{n}|a[i]|leq 10^9)

    题解

    https://www.cnblogs.com/zjp-shadow/p/9141971.html

    不难发现,成为最大前缀和位置 (p) 后面的所有前缀都不能 (>0)。如果 (>0) 那么后面必存在一点可以替换当前的答案。

    有了这个思路,那我们可以把每个序列拆成两段考虑,而分割点就是位置 (p)。记 (sum_s)(s) 这个状态所有点的代数和。

    1. (f(s)) 表示 (s) 集合的排列的最大前缀和等于 (sum_s) 且其他前缀和严格更小的方案数。这里规定其他前缀和更小是为了避免算重。

      那么若 (sum_s>0),我们便可以把它放到某个单点的后面。

      [f({u}) imes f(s) ightarrow f({u}cup s) ]

    2. (g(s)) 表示 (s) 集合的排列的所有前缀和都 (leq 0) 的方案数。

      (sum_{scup {u}}leq 0),那么我们便可以把 (s) 放到 (u) 的前面。

      [g(s) imes g({u}) ightarrow g(scup {u}) ]

    3. 答案为

      [ans=sum_s sum_s imes f(s) imes g([n]setminus s) ]

    时间复杂度 (O(2^n n))

    CO int N = 20;
    int n, sum[1 << N];
    int f[1 << N], g[1 << N];
    
    int main() {
        int n = read<int>();
        for (int i = 0; i < n; ++i) read(sum[1 << i]);
        for (int s = 0; s < 1 << n; ++s) sum[s] = sum[s ^ lowbit(s)] + sum[lowbit(s)];
        for (int i = 0; i < n; ++i) f[1 << i] = 1;
        for (int s = 0; s < 1 << n; ++s)
            if (sum[s] > 0)
                for (int i = 0; i < n; ++i)
                    if (~s >> i & 1)
                        f[s | 1 << i] = add(f[s | 1 << i], f[s]);
        g[0] = 1;
        for (int s = 0; s < 1 << n; ++s)
            if (sum[s] <= 0)
                for (int i = 0; i < n; ++i)
                    if (s >> i & 1)
                        g[s] = add(g[s], g[s ^ 1 << i]);
        int ans = 0;
        for (int s = 0; s < 1 << n; ++s) ans = add(ans, mul(sum[s] % mod + mod, mul(f[s], g[(1 << n) - 1 - s])));
        printf("%d
    ", ans);
        return 0;
    }
    

    以前的分析

    乘上(n!),所谓期望其实就是每种最大前缀和乘上方案数的乘积的和。

    参照Boss.Pi的题解。

    看数据范围,考虑状压dp。注意到前缀和的取值只有 (2^n) 种.

    然后可以枚举每一个集合的元素当最大前缀和 , 那么这个集合的元素排列之后每一个后缀都必须大于 (0) , 且这个集合的补集排列之后必须保证每一个前缀和都小于 (0).

    那么状压 DP 就行了 , 设 (f[i]) 表示集合 (i) 作为最大前缀和且排列之后每个后缀都大于 (0) 的方案数 , (g[i]) 表示集合 (i) 中元素排列之后每个前缀都小于 (0) 的方案数.

    强制 (f,g) 必须在合法的时候才能转移就行了.

    时间复杂度(O(n 2^n))

    关于转移的问题

    就算f加上j转移到了不合法的状态,以后也不会用到。

    所以是对的。

    co int N=22,mod=998244353;
    int n;
    int a[N];
    int sum[1<<N],f[1<<N],g[1<<N];
    
    il int add(int x,int y)
    {
    	return (x+y)%mod;
    }
    
    il int mul(int x,int y)
    {
    	return (ll)x*y%mod;
    }
    
    int main()
    {
    	read(n);
    	for(int i=0;i<n;++i)
    		read(sum[1<<i]);
    #define lowbit(x) (x&-x)
    	for(int i=0;i<(1<<n);++i)
    		sum[i]=sum[i-lowbit(i)]+sum[lowbit(i)];
    	for(int i=0;i<n;++i)
    		f[1<<i]=1,g[1<<i]=1;
    	for(int i=0;i<(1<<n);++i)
    	{
    		if(sum[i]>0)
    		{ // edit 1: the big brace is important
    			for(int j=0;j<n;++j)
    				if(~i>>j&1)
    					f[i|(1<<j)]=add(f[i|(1<<j)],f[i]);
    		}	
    		else
    		{
    			for(int j=0;j<n;++j)
    				if(~i>>j&1)
    					g[i|(1<<j)]=add(g[i|(1<<j)],g[i]);
    		}
    	}
    	g[0]=1;
    	int ans=0;
    	for(int i=0;i<(1<<n);++i)
    		if(sum[((1<<n)-1)^i]<=0)
    			ans=add(ans,mul(f[i],mul(sum[i],g[((1<<n)-1)^i])));
    	printf("%d",(ans+mod)%mod);
    	return 0;
    }
    

    Hint

    大括号的问题,害得我交了23次。

  • 相关阅读:
    Kth Smallest Element in a BST
    Bitwise AND of Numbers Range
    Happy Number
    Summary Ranges
    linux设置MySQL开机自动启动
    基本PSO算法实现(Java)
    Invert Binary Tree
    Rectangle Area
    Contains Duplicate II
    Contains Duplicate
  • 原文地址:https://www.cnblogs.com/autoint/p/12143069.html
Copyright © 2020-2023  润新知