• bzoj 5369 最大前缀和


    Written with StackEdit.

    Description

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

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

    (C)是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,

    现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上(n!)后对(998244353)取模的值,显然这是个整数。

    Input

    第一行一个正整数(n),表示序列长度。

    第二行(n)个数,表示原序列(a[1..n]),第(i)个数表示(a[i])

    (1≤n≤20,Sigma(|A_i|)<=10^9),其中(1<=i<=N.)

    Output

    输出一个非负整数,表示答案。

    Sample Input

    2
    -1 2

    Sample Output

    3

    Solution

    • 注意到(n)很小,每个子集的权值和我们可以暴力计算得出.
    • 直接考虑各个子集作为最大前缀和.
    • 显然,一个子集(S)排列后能成为最大前缀和,那么这个排列中不能有负的后缀和(否则去掉会更优),剩下的数排列后不能有正的前缀和(否则加上会更优).
    • 我们令(f[S])表示将(S)集合中的数排成没有负的后缀和的排列的方案数,(g[S])表示将(S)集合中的数排成没有正的前缀和的排列的方案.
    • 那么易知答案即为(sum_{Sin U,sum[S]>=0}f[S]*g[complement_{U}S]*sum[S]).
    • 考虑如何快速计算出(f)(g).若对于一个集合(i),新增了一个数(j).((j otin i)).
    • 我们可以将(i)任意排列,再将(j)放在最后,方案数为(f[i])(g[i]),统计入贡献.每个集合中的每个数都会被放在最后转移过来,所以总贡献一定是正确的.
    • 这样,只需要在加数的时候判断一下(sum[i])的符号,即可确定转移(f)(g).
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LoveLive;
    inline int read()
    {
    	int out=0,fh=1;
    	char jp=getchar();
    	while ((jp>'9'||jp<'0')&&jp!='-')
    		jp=getchar();
    	if (jp=='-')
    		{
    			fh=-1;
    			jp=getchar();
    		}
    	while (jp>='0'&&jp<='9')
    		{
    			out=out*10+jp-'0';
    			jp=getchar();
    		}
    	return out*fh;
    }
    const int P=998244353;
    const int MAXS=(1<<20)+10;
    inline int add(int a,int b)
    {
    	return (a + b) % P;
    } 
    inline int mul(int a,int b)
    {
    	return 1LL * a * b % P;
    }
    int a[21];
    int sum[MAXS],f[MAXS],g[MAXS];
    int n;
    inline int calc(int S)
    {
    	int res=0;
    	for(int i=0;i<n && S;++i,S>>=1)
    		if(S&1)
    			res+=a[i];
    	return res;
    }
    int main()
    {
    	n=read();
    	for(int i=0;i<n;++i)
    		a[i]=read();
    	int S=1<<n;
    	for(int i=0;i<S;++i)
    		sum[i]=calc(i);
    	for(int i=0;i<n;++i)
    		f[1<<i]=1,g[1<<i]=1;
    	for(int i=0;i<S;++i)
    		{
    			if(sum[i]>0)
    				{
    					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]);
    				}
    		}
    	int ans=0;
    	int U=S-1;
    	g[0]=1;
    	for(int i=0;i<S;++i)
    		if(sum[U^i]<=0)
    			ans=add(ans,mul(mul(f[i],sum[i]),g[U^i]));
    	printf("%d
    ",add(ans,P));
    	return 0;
    }
    
  • 相关阅读:
    ansible api调用详解
    多线程使用不当导致的 OOM
    常用Excel工具小结
    lazarus 检测内存泄漏
    用lazarus创建linux的菜单、桌面快捷方式及文件关联
    tkinter窗口程序设计
    vim 从嫌弃到依赖(23)——最后的闲扯
    vim 从嫌弃到依赖(22)——自动补全
    从零开始匹配vim(0)——vimscript 简介
    vim 从嫌弃到依赖(21)——跨文件搜索
  • 原文地址:https://www.cnblogs.com/jklover/p/10024388.html
Copyright © 2020-2023  润新知