bzoj5369 [Pkusc2018]最大前缀和
题目描述:
题解:
不带组合数玩的计数问题。
先只考虑最大前缀和大于$0$的情况。
首先对于一个数列,他的最大前缀和有一个性质,即(高能预警):
(分割线的意思是满足该线左边为最大前缀的,最靠左的分割线)
分割线左边的数列,任意后缀都必须$>0$;
分割线右边的数列,任意前缀都必须$\leq 0$。
这样满足最大。
所以状压一波,转移可以按照在当前状态后边(或者前边)再接一个数来转移。
然后我脑残一交发现只有$55$。
后来注意到前缀必须从$1$开始,就发现大事不妙。
其实也很简单。当$sum = \sum a_i < 0$时会出现负的最大前缀。
怎么算呢?
这个最大前缀和还有一个性质:
分割线左边的每一个后缀(除了整个前缀)都$>0$;
分割线右边的每一个前缀都$\leq 0$。
所以左边第一位(即开头)一定是负数,然后左边第二位到分割线和分割线右边的状态数我们已经算完了。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 22; const int M = (1<<20)+50; const int MOD = 998244353; template<typename T> inline void read(T&x) { T f = 1,c = 0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();} x = f*c; } int n; ll a[N],ans; void Mod(ll&x){if(x>=MOD)x-=MOD;} ll cot[M],_id[M]; ll c0[M],c1[M];//<= > int main() { // freopen("tt.in","r",stdin); read(n);int MX = (1<<n)-1; ll sum = 0; for(int i=0;i<n;i++)read(a[i]),_id[1<<i]=i,sum+=a[i]; for(int i=1;i<(1<<n);i++)cot[i]=cot[i^(i&-i)]+a[_id[i&-i]]; c0[0]=c1[0]=1; for(int i=0;i<(1<<n);i++)for(int j=0;j<n;j++)if(!(i&(1<<j))) { if(cot[i]+a[j]<=0)Mod(c0[i|(1<<j)]+=c0[i]); else Mod(c1[i|(1<<j)]+=c1[i]); } for(int i=0;i<(1<<n);i++)Mod(ans+=c0[i]*c1[MX^i]%MOD*cot[MX^i]%MOD); if(sum<0) { for(int i=0;i<(1<<n);i++)if(!i||cot[i]>0) for(int j=0;j<n;j++)if(!(i&(1<<j))&&cot[i]+a[j]<0) Mod(ans+=c1[i]*c0[MX^(i|(1<<j))]%MOD*(MOD+cot[i]+a[j])%MOD); } printf("%lld\n",ans); return 0; }