G - X 解説 by penguinman


バツ印を書くために必要な線分の本数の最小値は、以下の値と等しいです。

  • マス (i,j)(i,j) にバツ印が付けられていてかつマス (i1,j1)(i-1,j-1) にバツ印が付けられていないような整数対 (i,j)(i,j) の個数と、マス (k,l)(k,l) にバツ印が付けられていてかつマス (k1,l+1)(k-1,l+1) にバツ印が付けられていないような整数対 (k,l)(k,l) の個数の合計(ここで、マス (i1,j1),(k1,l+1)(i-1,j-1),(k-1,l+1) が存在しない場合にもそれらのマスにはバツ印が書かれていないと見做す)

故にこの問題は、以下のように表現することができます。

各マスに対して、バツ印を付けるか付けないかの 22 状態を割り振る。マス (i,j)(i,j) にバツ印を付けるとスコアに Ai,jA_{i,j} が加算され、付けないと何も加算されない。

そしてまた一方で、マス (i,j)(i,j) にバツ印が付けられていてかつマス (i1,j1)(i-1,j-1) にバツ印が付けられていないような整数対 (i,j)(i,j)、およびマス (k,l)(k,l) にバツ印が付けられていてかつマス (k1,l+1)(k-1,l+1) にバツ印が付けられていないような整数対 (k,l)(k,l)11 つ存在するごとにそれぞれスコアから CC が減算される。

得られるスコアを最大化せよ。

このままだと加算と減算が混ざっていて見通しが悪いので、以下のように加算のみに統一します。

各マスに対して、バツ印を付けるか付けないかの 22 状態を割り振る。マス (i,j)(i,j) にバツ印を付けないとコストに Ai,jA_{i,j} が加算され、付けると何も加算されない。

そしてまた一方で、マス (i,j)(i,j) にバツ印が付けられていてかつマス (i1,j1)(i-1,j-1) にバツ印が付けられていないような整数対 (i,j)(i,j)、およびマス (k,l)(k,l) にバツ印が付けられていてかつマス (k1,l+1)(k-1,l+1) にバツ印が付けられていないような整数対 (k,l)(k,l)11 つ存在するごとにそれぞれコストに CC が加算される。

Ai,jA_{i,j} の総和からコストを引いた値を最大化せよ。転じて、合計コストを最小化せよ。

ここまで来るとかなり見通しがよいです。このコストを最小化する問題は、以下のように構築されるグラフにおける、始点、終点の 22 頂点に対する最小カット問題に帰着させることが可能です。

  1. 各頂点がグリッドの各マスに対応した、H×WH \times W 頂点 00 辺のグラフを用意する。さらに、そのグラフにそれぞれ始点、終点となる 22 頂点を追加する
  2. 任意のマス (i,j)(i,j) に対し、以下の 11 本の辺を張る。
    • 始点からマス (i,j)(i,j) への重み Ai,jA_{i,j} の有向辺
  3. マス (i1,j1)(i-1,j-1) が存在しないような任意のマス (i,j)(i,j) に対し、以下の 11 本の辺を張る。
    • マス (i,j)(i,j) から終点への重み CC の有向辺
  4. マス (i1,j1)(i-1,j-1) が存在するような任意のマス (i,j)(i,j) に対し、以下の 22 本の辺を張る。
    • マス (i,j)(i,j) から終点への重み 00 の有向辺
    • マス (i,j)(i,j) からマス (i1,j1)(i-1,j-1) への重み CC の有向辺
  5. マス (i1,j+1)(i-1,j+1) が存在しないような任意のマス (i,j)(i,j) に対し、以下の 11 本の辺を張る。
    • マス (i,j)(i,j) から終点への重み CC の有向辺
  6. マス (i1,j+1)(i-1,j+1) が存在するような任意のマス (i,j)(i,j) に対し、以下の 22 本の辺を張る。
    • マス (i,j)(i,j) から終点への重み 00 の有向辺
    • マス (i,j)(i,j) からマス (i1,j+1)(i-1,j+1) への重み CC の有向辺

これをそのままコードに移しましょう。

計算量は Dinic 法を用いると O(HWHW)O(HW \sqrt{HW}) となります

計算量の解析に誤りがありました。 グラフの頂点数、辺数を V,EV, E としたとき Dinic 法の計算量は O(EV2)O(EV^2) となるため、O(H3W3)O(H^3W^3) となります。または、CCAi,jA_{i,j} の値がすべて KK 以下であるとしたとき、O(KHWHW)O(KHW \sqrt{HW}) となります。 これで通ることが証明できる十分小さい制約で出題すべきでした。申し訳ございません。

C++ の場合、atcoder library を利用すると非常に便利です。

実装例 (C++)

Copy
  1. #include<bits/stdc++.h>
  2. #include<atcoder/maxflow>
  3. using namespace std;
  4. using namespace atcoder;
  5. #define rep(i, j, n) for(int i = int(j); i < int(n); i++)
  6. using ll = long long;
  7. int main(){
  8. int H,W,C; cin >> H >> W >> C;
  9. mf_graph<ll> graph(H*W+2);
  10. vector<vector<int>> A(H,vector<int>(W));
  11. rep(i,0,H){
  12. rep(j,0,W) cin >> A[i][j];
  13. }
  14. int sz = H*W;
  15. rep(i,0,H){
  16. rep(j,0,W){
  17. int now = i*W+j;
  18. graph.add_edge(sz,now,A[i][j]);
  19. }
  20. }
  21. rep(i,0,H){
  22. rep(j,0,W){
  23. int now = i*W+j;
  24. if(0 < i && 0 < j){
  25. int next = (i-1)*W+(j-1);
  26. graph.add_edge(now,sz+1,0);
  27. graph.add_edge(now,next,C);
  28. }
  29. else{
  30. graph.add_edge(now,sz+1,C);
  31. }
  32. }
  33. }
  34. rep(i,0,H){
  35. rep(j,0,W){
  36. int now = i*W+j;
  37. if(0 < i && j+1 < W){
  38. int next = (i-1)*W+(j+1);
  39. graph.add_edge(now,sz+1,0);
  40. graph.add_edge(now,next,C);
  41. }
  42. else{
  43. graph.add_edge(now,sz+1,C);
  44. }
  45. }
  46. }
  47. ll ans = 0;
  48. rep(i,0,H){
  49. rep(j,0,W) ans += A[i][j];
  50. }
  51. ans -= graph.flow(sz,sz+1);
  52. cout << ans << endl;
  53. }
#include<bits/stdc++.h>
#include<atcoder/maxflow>
using namespace std;
using namespace atcoder;

#define rep(i, j, n) for(int i = int(j); i < int(n); i++)

using ll = long long;

int main(){
    int H,W,C; cin >> H >> W >> C;
    mf_graph<ll> graph(H*W+2);
    vector<vector<int>> A(H,vector<int>(W));
    rep(i,0,H){
        rep(j,0,W) cin >> A[i][j];
    }
    int sz = H*W;
    rep(i,0,H){
        rep(j,0,W){
            int now = i*W+j;
            graph.add_edge(sz,now,A[i][j]);
        }
    }
    rep(i,0,H){
        rep(j,0,W){
            int now = i*W+j;
            if(0 < i && 0 < j){
                int next = (i-1)*W+(j-1);
                graph.add_edge(now,sz+1,0);
                graph.add_edge(now,next,C);
            }
            else{
                graph.add_edge(now,sz+1,C);
            }
        }
    }
    rep(i,0,H){
        rep(j,0,W){
            int now = i*W+j;
            if(0 < i && j+1 < W){
                int next = (i-1)*W+(j+1);
                graph.add_edge(now,sz+1,0);
                graph.add_edge(now,next,C);
            }
            else{
                graph.add_edge(now,sz+1,C);
            }
        }
    }
    ll ans = 0;
    rep(i,0,H){
        rep(j,0,W) ans += A[i][j];
    }
    ans -= graph.flow(sz,sz+1);
    cout << ans << endl;
}

投稿日時:
最終更新:



2021-10-30 (土)
19:00:10 +00:00