Official

G - Specified Range Sums Editorial by physics0523


唐突ですが、 \(\sum_{j=l}^{r} A_j = S_i\) という制約は扱いづらいため、 \(B(k) = \sum_{i=1}^{k} A_i\) と置き換えましょう。
すると、 \(\sum_{j=l}^{r} A_j = S_i\) という制約は \(B_{r} - B_{l-1} = S\) という形に置き換わります。

ここで、改めて数列に課されている制約を \(B\) を使って整理します。

  • \(B_{R_i} - B_{L_i-1} = S_i\) ( \(1 \le i \le M\) )
  • \(B_{j} - B_{j-1} \ge 1\) ( \(1 \le j \le N\) )

この制約を更に加工し、等号を不等号にします。

  • \(B_{R_i} - B_{L_i-1} \ge S_i\) ( \(1 \le i \le M\) )
  • \(B_{R_i} - B_{L_i-1} \le S_i\) ( \(1 \le i \le M\) )
  • \(B_{j} - B_{j-1} \ge 1\) ( \(1 \le j \le N\) )

さて、解くべき問題は、次の線形計画問題として表現されます。

\[ \begin{aligned} &\text{minimize} & & B_N \\ &\text{subject to} & & B_{R_i} - B_{L_i-1} \ge S_i \ ( 1 \le i \le M ) \\ & & & B_{R_i} - B_{L_i-1} \le S_i \ ( 1 \le i \le M ) \\ & & & B_{j} - B_{j-1} \ge 1\ ( 1 \le j \le N ) \\ & & & B_0 = 0 \end{aligned} \]

ここで、全ての制約式が \(B_i + c \ge B_j\) の形で表現されることに注目します。これは特別な解きやすい形の線形計画問題で、 牛ゲー と呼ばれます。解説1 解説2 解説3 解説4 解説5
( これが 牛ゲー と呼ばれている理由は、このテクニックが浸透するに至った問題が「牛を並べる問題」であったからです。 移植1 移植2 )

牛ゲーそのものの目的関数は「特定の変数の最大化」ですが、線形計画問題を以下のように設定すれば問題ありません。
この問題の答えは以下の線形計画問題の最適解の目的関数の \(-1\) 倍となります。

\[ \begin{aligned} &\text{maximize} & & B_0 \\ &\text{subject to} & & B_{R_i} - B_{L_i-1} \ge S_i \ ( 1 \le i \le M ) \\ & & & B_{R_i} - B_{L_i-1} \le S_i \ ( 1 \le i \le M ) \\ & & & B_{j} - B_{j-1} \ge 1\ ( 1 \le j \le N ) \\ & & & B_N = 0 \\ \end{aligned} \]

あとは、この問題の形に従い適切なグラフを構築して Bellman-Ford 法で最短経路問題を解くと、この問題に正解できます。

実装例 (C++) :

#include<bits/stdc++.h>

using namespace std;

int main(){
  long long n,m;
  cin >> n >> m;
  vector<long long> l(m),r(m),s(m);
  for(long long i=0;i<m;i++){
    cin >> l[i] >> r[i] >> s[i];
  }

  vector<array<long long,3>> eg;
  for(long long i=1;i<=n;i++){
    eg.push_back({i,i-1,-1});
  }
  for(long long i=0;i<m;i++){
    eg.push_back({r[i],l[i]-1,-s[i]});
    eg.push_back({l[i]-1,r[i],s[i]});
  }

  // for(auto &nx : eg){
  //   cout << nx[0] << " -> " << nx[1] << " : " << nx[2] << "\n";
  // }

  vector<long long> d(n+1,8e18);
  d[n]=0;
  bool upd;
  for(long long tr=0;tr<2*(n+m)+5;tr++){
    upd=false;
    for(auto &nx : eg){
      long long nd=d[nx[0]]+nx[2];
      if(d[nx[1]] > nd){
        d[nx[1]]=nd;
        upd=true;
      }
    }
  }
  
  if(upd){cout << "-1\n"; return 0;}
  cout << -d[0] << "\n";
  return 0;
}

posted:
last update: