LOJ 6433 「PKUSC2018」最大前缀和——状压DP

题目:https://loj.ac/problem/6433

想到一个方案中没有被选的后缀满足 “该后缀的任一前缀和 <=0 ”。

于是令 dp[ S ] 表示选了点集 S ,满足任一前缀和 <=0 的方案。很好转移。

令 f[ S ] 表示选了点集 S ,且 S 整体就是最大前缀和的方案。

只会 3n 做出 f[ ] ,就是考虑容斥, \( f[s]=|s|! - \sum f[d]*dp[s^d] (sm[d]>=sm[s]) \) ,其中 sm[ s ] 表示点集 s 的权值和。

然后看了题解。发现 “ S 整体是最大前缀和 ” <==> “ S 的任一后缀和 >0 ” ,所以像做 dp[ ] 一样,从后往前放数字就能转移了!!!

还要训练思维。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int Mx(int a,int b){return a>b?a:b;}
const int N=25,M=(1<<20)+5,mod=998244353;
int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}

int n,a[N],dp[M],f[M]; ll sm[M];
int ct[M],bin[N],lg[M],jc[N];
namespace S1{
  void solve(int cnt)
  {
    int ans=0;for(int i=1;i<=n;i++)ans=upt(ans+a[i]);
    for(int i=1;i<=n;i++)ans=(ll)ans*i%mod;
    if(!cnt||n==1){printf("%d\n",ans); return;}//n==1!!
    int rt,U=bin[n]-1;
    for(int i=1;i<=n;i++)if(a[i]<0){rt=i-1;break;}//i-1 for bin[]
    for(int s=0;s<U;s++)//s<U
      if((s&bin[rt])&&sm[s]<0)
    ans=upt(ans-(ll)jc[ct[U^s]]*jc[ct[s]-1]%mod*sm[s]%mod);
    printf("%d\n",ans);
  }
}
namespace S2{
  int dp[M][85],mn[M],mx[M],fx=40;
  void init()
  {
    for(int s=1;s<bin[n];s++)
      for(int i=1;i<=n;i++)
    if(s&bin[i-1])
      {if(a[i]>0)mx[s]+=a[i];else mn[s]+=a[i];}
  }
  void solve()
  {
    init(); dp[0][0]=1;int U=bin[n]-1;
    mn[0]=mx[0]=-40;///
    //0 not 0+fx for 'none' can't be cal
    for(int s=0;s<U;s++)
      {
    int tp=U^s,d;
    while(tp)
      {
        d=tp&-tp; tp^=d; d|=s;
        for(int i=mn[s];i<=mx[s];i++)
          if(dp[s][i+fx])
        {
          int j=Mx(i,sm[d])+fx;
          dp[d][j]=upt(dp[d][j]+dp[s][i+fx]);
        }
      }
      }
    int ans=0;
    for(int i=mn[U];i<=mx[U];i++)
      if(dp[U][i+fx])
    ans=(ans+(ll)dp[U][i+fx]*i)%mod;
    printf("%d\n",upt(ans));//upt
  }
}
void init()
{
  bin[0]=1; lg[1]=0;
  for(int i=1;i<=n;i++)
    bin[i]=bin[i-1]<<1,lg[bin[i]]=i;
  jc[0]=1;for(int i=1;i<=n;i++)jc[i]=(ll)jc[i-1]*i%mod;
  for(int s=1;s<bin[n];s++)
    {
      sm[s]=sm[s^(s&-s)]+a[lg[s&-s]+1];
      ct[s]=ct[s^(s&-s)]+1;
    }
}
int main()
{
  scanf("%d",&n); bool fg=0;int cnt=0;
  for(int i=1;i<=n;i++)
    {
      scanf("%d",&a[i]);
      if(a[i]<-2||a[i]>2)fg=1;
      if(a[i]<0)cnt++;
    }
  init();
  if(cnt<=1){S1::solve(cnt);return 0;}
  if(!fg){S2::solve();return 0;}
  dp[0]=1;
  for(int s=1;s<bin[n];s++)
    {
      if(sm[s]>0)continue;
      for(int i=1;i<=n;i++)
    if(s&bin[i-1])
      {
        int d=s^bin[i-1];
        if(dp[d])dp[s]=upt(dp[s]+dp[d]);
      }
    }
  f[0]=1;
  for(int s=1;s<bin[n];s++)
    for(int i=1;i<=n;i++)
      if(s&bin[i-1])
    {
      int d=s^bin[i-1];
      if(sm[d]>0||!d)f[s]=upt(f[s]+f[d]);//!d
    }
  int ans=0;
  for(int s=1,U=bin[n]-1;s<bin[n];s++)
    if(f[s]&&dp[U^s])
      ans=(ans+sm[s]%mod*f[s]%mod*dp[U^s])%mod;
  printf("%d\n",upt(ans));
  return 0;
}

 

posted on 2019-05-22 09:02  Narh  阅读(210)  评论(0编辑  收藏  举报

导航