P14 The 4th Universal Cup. Stage 20 G. Sequence Domination

Problem G. Sequence Domination

思路

  • 首先对原公式化简: \(\sum_{i=1}^N A_iV_i \geq \sum_{i=1}^N B_iV_i \Leftrightarrow \sum_{i=1}^N (A_i - B_i)V_i \geq0\) , 令 \(d_i = A_i - B_i\), 原不等式变为 \(\sum_{i=1}^N d_iV_i \geq 0\), \(d_i \in [-S,S]\) . (这里令 \(S = M - 1\))
  • 注意到 \(V_i\) 可以为 \(0\) , 且根据条件有合法 \(\{V_n\}=[..., 8, 4, 2, 1, 1]\) 这种形式, 所以大胆猜测: 令 \(w_i = d_{i-1} + 2d_{i-2} + 4d_{i-3} +...\) , 有 \(\forall i \in [1,n]\) , \(d_i + w_i \geq 0\) , 那么容易看出 \(w_{i+1} = d_i + 2w_i\) , \(w_i \geq 0\) . (猜了就不管证明了)
  • 所以现在如果找到了一个合法的序列 \(\{d\}\) , 其对答案的贡献将会是 \(\prod_{i=1}^N(M-|d_i|)\) .
  • 然后可以设计动态规划 \(dp[i][j]\) 表示考虑前 \(i\) 位, \(w_i = j\)\(d_1 \sim d_{i-1}\) 的方案数. 对吗? 不对! 我卡死在这半天...因为最后要的压根就不是 \(\{d\}\) 的方案数, 而是上面提到的连乘! 所以 \(dp[i][j]\) 表示的应当是加权方案数. 具体接着看...
  • 那么在更新的时候, \(d_i\) 的取值会是 \([-w_i, S]\) , 由此更新 \(dp[i+1][2*j+d_i] += dp[i][j] * (M - |d_i|)\) . 这里可以看出, 其实我们压根不关心第 \(i\) 位之前 \(\{d\}\) 长啥样, 我们只关注 \(w_i\) 到底是多少, 由此来得到答案, 也就是说 \(dp\) 是分阶段的, 前面阶段具体的内容不会影响后续阶段, 也就是伟大的 "无后效性"!
  • 注意到, 当 \(w_i \geq S\) 的时候, \(w_{i+1} \geq S\) , 且 \(d_i\) 所有值都可以取到, 所以其实可以把大于 \(S\) 的情况全部都当作 \(S\), 答案不会变化.
  • 至此, 大体思路便出来了.

写出 \(O(NM^2)\) 的代码:

inline void solve(int &n,int &m){
  int s=m-1;//di=ai-bi -> [-s,s]
  memset(f,0,(n+2)*sizeof(f[0]));
  f[1][0]=1;//till i, wi=j
  for(int i=2;i<=n;++i){
    for(int j=0;j<=s;++j){
      for(int di=-j;di<=s;++di)
        (f[i][min(2*j+di,s)]+=f[i-1][j]*(m-abs(di)))%=mod;
    }
  }
  ll ans=0;
  for(int j=0;j<=s;++j)
    for(int di=-j;di<=s;++di)
      (ans+=f[n][j]*(m-abs(di)))%=mod;
  cout<<ans<<'\n';
}

优化

  • 显然速度还不够, 观察 \(dp\) 原理. \(f[i][j]\) 会对 \(f[i+1][2*j+d_i]\) 做出 \(f[i][j] * (S - |d_i|)\), 也就是说第二维度和权重都是在线性变化的, 可以用 二阶差分 进行优化.
  • 一阶差分很好理解, 对应的作用是区间加减同一数值. 二阶差分其实就是对一阶进行区间加减, 那么对应到原数组就会是区间加减等差数列了. 所以如果要对 \(f[i+1][j\sim 2*j]\) 对应分别加上 \(f[i][j] * (M - (j\sim 0))\), 其实就是把一阶差分和二阶差分结合起来一起使用.
  • 具体来说, 开两个数组分别存储一阶和二阶差分数值. 那么对于每一个 \(f[i-1][j]\) , 我们要把 \(f[i][j\sim 2j]\) 分别加上 \(f[i-1][j]*(M - (j\sim 0))\) ; 把 \(f[i][(2*j+1)\sim (2*j + s)]\) 分别加上 \(f[i][j]*(M - (1\sim s))\). 那么对于第一段加的权重部分, 本质上就是把 \(1,2,3,...\) 的等差数列整体加了个 \(M-j-1\) , 而对于右边这一段, 其实就是个递减等差数列. 直接维护即可.

O(NM) AC代码:

const int N=5005,mod=998244353;

ll f[N][N];

inline void solve(int &n,int &m){
  int s=m-1;//di=ai-bi -> [-s,s]
  memset(f,0,(n+2)*sizeof(f[0]));
  f[1][0]=1;//till i, wi=j
  for(int i=2;i<=n;++i){
    int mx=-1;
    vector<ll> dif1(3*m+2,0),dif2(3*m+2,0);
    for(int j=0;j<=s;++j){
      dif1[j]+=(m-j-1)*f[i-1][j]%mod;
      dif1[2*j]-=(m-j-1)*f[i-1][j]%mod;
      dif2[j]+=1*f[i-1][j];
      dif2[2*j]-=1*f[i-1][j];
      dif1[2*j]-=j*f[i-1][j];

      dif1[2*j]+=(m+1)*f[i-1][j]%mod;
      dif2[2*j]-=1*f[i-1][j];
      dif2[2*j+s+2]+=1*f[i-1][j];
    }
    for(int j=0;j<=3*s;++j){
      (dif1[j]+=dif2[j]+mod)%=mod;
      (dif2[j+1]+=dif2[j]+mod)%=mod;
    }
    for(int j=0;j<=3*s;++j){
      (f[i][min(j,s)]+=dif1[j]+mod)%=mod;
      (dif1[j+1]+=dif1[j]+mod)%=mod;
    }
  }
  ll ans=0;
  for(int j=0;j<=s;++j)
    for(int di=-j;di<=s;++di)
      (ans+=f[n][j]*(m-abs(di)))%=mod;
  cout<<ans<<'\n';
}

· EOF

posted @ 2026-03-31 10:01  Paburichenko  阅读(2)  评论(0)    收藏  举报