読者です 読者をやめる 読者になる 読者になる

naoya_t@hatenablog

いわゆるチラシノウラであります

SRM533 - 深夜開催のSRMがちょっと辛い今日この頃

最近ちょっと早寝早起きサイクルになってるので2amからとかちょっときついですね。

というわけでSRM533の問題を見てみた話。配点的には250-500-1000ですが・・・
出てたら430位ぐらいでレーティング横ばい〜微減、かな。

Div1 Easy(250) CasketOfStar

250がちょっと難しかった、というか正しい筋道で考えるまでに時間がかかった。

  • 両端から1つずつ減らして f(a,b) = weight[a]*weight[b] + max(f(a,b-1), f(a+1,b)) みたいに解けるからDPだ、と考えていた時期が僕にもありました、みたいな
  • これではサンプルケースさえ解けない。貪欲に大きい方から行くとかも何か違いそうというか怖い
  • サンプル#2の {2,2,7,6,90,5,9} の例で、90 のところで左右に分けて考えられそうな気がしたので、単にあらゆる分割点を考えて f(a,b) = weight[a]*weight[b] + max( f(a,a+1)+f(a+1,b), f(a,a+2)+f(a+2,b), ..., f(a,b-1)+f(b-1,b) ) ではどうか。f(a,b) はb-a=1のとき0とすればよさげ。
  • というわけでやっぱりDP。メモ化再帰で書いたけど。
  • これでAC。110.83点(46'44'')
#define found(s,e)  ((s).find(e)!=(s).end())

typedef pair<int,int> i_i;

map<i_i,int> mm;
int sub(const vector<int>& ws, int b, int e){
  i_i k(b,e); if (found(mm,k)) return mm[k];
  if (e-b <= 1) return 0;
  int s = ws[b] * ws[e];
  if (e-b == 2) return s;

  int mx = 0;
  for (int i=b+1; i<=e-1; i++) {
    mx = max(mx, sub(ws,b,i) + sub(ws,i,e));
  }
  return mm[k] = s + mx;
}

class CasketOfStar {
 public:
  int maxEnergy(vector<int> weight) {
    mm.clear();
    return sub(weight, 0, weight.size()-1);
  }
};

Div1 Medium(500) MagicBoard

51'12''かけて解いた答えが単純なテスト {"#", "#"} で落ちた。原因は S が水平方向の移動から始まらないといけないのを忘れていたから。SRMでドジっ子アピールしても何の得もないというのに。

  • 一筆書きできるかどうか?
  • いや、縦横縦横みたいな感じの移動でないと駄目みたい
  • サンプルの最後の58(わぁい58 あかり58大好き)は間にスペースがある。移動は同一行ないし同一列ならどう飛んでもよいようだ。
  • 頂点が最大2500。このどこか1つから、辺を全部(最大5000*2500ぐらい)列挙して縦横縦横縛りで移動しながらTLEせずに全部回れるのか?いや無理だろう
  • 横軸から縦軸へ。縦軸から横軸へ。移動できるのはMagic Diamondのある箇所のみ。
  • キーボードの配線みたいだ。そして2部グラフっぽい。
  • 縦軸、横軸をノードとして、Magic Diamondを辺とすると、すべてのMagic Diamondをさらうのとすべての辺を通るのが同じなので、これはシンプルな一筆書き問題に帰着できる
  • UnionFindか何かで全ノードの接続を確認した上で、各ノードから出てる辺の数を数えて、奇数の箇所が0か2なら一筆書きが可能、っていうあれだ(thanks to オイラー先生)
  • コード書けた。サンプル通った。
  • 提出。211.82点(51'12'')Easyで時間食ってたからこれでは間に合わない。
  • しかもWA。単純なテスト {"#", "#"} で落ちてる。
  • ああああああ
  • S は水平方向の移動から始まらないといけないのを忘れてた
  • 奇数辺のノードが2つある場合、それが縦軸ノードに2つあったらアウトだ
  • そこだけ直してAC
  • 問題の制約はちゃんと把握しましょう。サンプルケースが足りなければ書き足しましょう。
#include <algorithm>
#include <string>
#include <vector>
#include <set>
using namespace std;
#define rep(var,n)  for(int var=0;var<(n);var++)
typedef pair<int,int> i_i;

class UnionFind {
  vector<int> data;
 public:
  explicit UnionFind(int size) : data(size, -1) { }
  bool unionSet(int x, int y) {
    x = root(x);
    y = root(y);
    if (x != y) {
      if (data[y] < data[x]) swap(x, y);
      data[x] += data[y];
      data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) { return root(x) == root(y); }
  int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
  int size(int x) { return -data[root(x)]; }
  bool has(int x) { return data[x] >= 0; }
};

class MagicBoard {
 public:
  string ableToUnlock(vector<string> board) {
    int R = board.size(), C = board[0].size();
    // vector<set<i_i> > h(R), v(C);
    vector<set<int> > hv(R), vh(C);
    UnionFind uf(R+C);
    rep(r,R) rep(c,C) if (board[r][c] == '#') {
      i_i rc(r,c);
      // h[r].insert(rc); v[c].insert(rc);
      hv[r].insert(c); vh[c].insert(r);
      uf.unionSet(r, R+c);
    }

    int root = 10000;
    // int odd = 0, even = 0;
    int r_odd = 0, c_odd = 0;
    rep(r,R) {
      if (hv[r].size() == 0) continue;
      if (root == 10000) root = uf.root(r);
      else if (uf.root(r) != root) return "NO";
      // if (hv[r].size() & 1) odd++; else even++;
      if (hv[r].size() & 1) r_odd++;
    }
    rep(c,C) {
      if (vh[c].size() == 0) continue;
      else if (uf.root(R+c) != root) return "NO";
      // if (vh[c].size() & 1) odd++; else even++;
      if (vh[c].size() & 1) c_odd++;
    }

    // return odd <= 2 ? "YES" : "NO";
    if (r_odd + c_odd > 2) return "NO";
    if (r_odd == 2) return "NO";
    return "YES";
  }
};