ferinの競プロ帳

競プロについてのメモ

AOJ2425 全探索お姉さんの休日

問題ページ
A Holiday of Miss Brute Force | Aizu Online Judge

考えたこと

  • 六角形のグリッド上を探索する
  • 拡張dijkstraの要領で頂点に時間を増やせばいい気持ちになる
  • 時間の上限ないしそのままつけるのは無理
  • 時間が影響するのはabs(x*y*t)%6の指示される方向を計算する部分だけ
  • 時間はmod 6で持っていても問題なさそう
  • d[y][x][t] = (座標y,xに時間tでいるときの最小の指示を無視する回数) として拡張dijkstraをする
  • 六角形のグリッドでは位置によってdx,dyが変化するのに注意しつつ実装すると通る

ソースコード

#define __USE_MINGW_ANSI_STDIO 0
#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> bool IN(T a, T b, T x) { return a<=x&&x<b; }

int dx[] = {0, 1, 1, 0, -1, -1}, dy1[] = {1, 0, -1, -1, -1, 0}, dy2[] = {1, 1, 0, -1, 0, 1};

// d[y][x][t%6] = (ここにたどり着く最小の無視した回数)
int d[250][250][6];
int board[250][250];
signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  int sx, sy, gx, gy;
  cin >> sx >> sy >> gx >> gy;
  int n;
  cin >> n;
  REP(i, n) {
    int x, y;
    cin >> x >> y;
    board[y+100][x+100] = 1;
  }
  int lx, ly;
  cin >> lx >> ly;

  REP(i, 250) REP(j, 250) REP(k, 6) d[i][j][k] = LLINF;
  d[100+sy][100+sx][0] = 0;
  priority_queue<VI, VVI, greater<VI>> que;
  que.push({d[100+sy][100+sx][0], 100+sy, 100+sx, 0});

  while(que.size()) {
    VI v = que.top(); que.pop();
    // cout << v << endl;
    int x = v[2], y = v[1], t = v[3];
    REP(i, 6) {
      int nx = x + dx[i], ny = y + (x%2==0?dy1:dy2)[i];
      if(100-lx <= nx && nx <= 100+lx && 100-ly <= ny && ny <= 100+ly && board[ny][nx]==0) {
        int tmp = abs((x-100)*(y-100)*t)%6 != i;
        // cout << i << " " << nx << " " << ny << " " << tmp << endl;
        if(d[ny][nx][(t+1)%6] > d[y][x][t] + tmp) {
          d[ny][nx][(t+1)%6] = d[y][x][t] + tmp;
          que.push({d[ny][nx][(t+1)%6], ny, nx, (t+1)%6});
        }
      }
    }
    if(d[y][x][(t+1)%6] > d[y][x][t] + 1) {
      d[y][x][(t+1)%6] = d[y][x][t] + 1;
      que.push({d[y][x][(t+1)%6], y, x, (t+1)%6});
    }
  }

  int ans = LLINF;
  REP(i, 6) {
    chmin(ans, d[100+gy][100+gx][i]);
    // cout << d[100+gy][100+gx][i] << endl;
  }
  if(ans==LLINF) cout << -1 << endl;
  else cout << ans << endl;

  return 0;
}

RUPC2018 day2 J Matrix

問題ページ

解法

ある矩形範囲(a,b,c,d)の最大値はその区間の max(A)*max(B), max(A)*min(B), min(A)*max(B), min(A)*min(B) のうちのどれかとなる。最小値についても同様にどれかになる。
最大値の個数については max(A)*max(B)が最大値であればmax(A)の個数*max(B)の個数, max(A)*min(B)が最大値であればmax(A)の個数*min(B)の個数,… の和によって求められる。
max(A)==min(A)のケースや答えが0になるケースに注意が必要。maxが0のときについて考えると、行列Cの要素のうち+になるようなものが存在してはいけないのでAとBが取る数の符号が異なる場合のみのはずである。したがって (min(A)>=0 かつ max(b)<=0) もしくは (max(A) <= 0 かつ min(B) >= 0) となっているはずで最大値の候補に上げた4個の積のうち0になるものが必ず存在する。つまりans=0のときも値については上に上げた4つのmaxを取ることで求められる。0になる個数を求めるのは矩形範囲全体から0でないものを引くとすればよい。minについても同様。

したがって区間加算区間max,min,countが可能な遅延セグメントツリーを使えばよい。この記事のように抽象化した遅延セグ木でどう実装したのか書く。(最大,最大の個数,最小,最小の個数)がほしいのでTはvector、Eにはその区間全体に加算する値がほしいのでintとする。マージする写像fでは最大,最小はmax,minを取る、個数は左右のノードを見てmaxとノードの値が一致すればそのノードの個数をプラスするとした。写像gでは区間加算なので最大と最小にだけプラスし個数には何もしない。写像hでは区間加算なので和を返す。単位元d0は(0,1,0,1)、d1は0とする。

まとめると遅延セグメントツリーを使いクエリに答えていく。下の実装はdefine int llしてるので注意。

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define PB push_back

const ll LLINF = (1LL<<60);

// 遅延セグメントツリー
template <typename T, typename E>
struct segtree {
  using F = function<T(T,T)>;
  using G = function<T(T,E)>;
  using H = function<E(E,E)>;
  using P = function<E(E,int)>;
  F f; G g; H h; P p; T d1; E d0;
  int n;
  vector<T> dat;
  vector<E> lazy;

  segtree(){}
  segtree(int n_, F f_, G g_, H h_, T d1_, E d0_, P p_=[](E a, int b){return a;}):
    f(f_), g(g_), h(h_), p(p_), d1(d1_), d0(d0_) {
    n = 1; while(n < n_) n *= 2;
    dat.assign(n*2, d1);
    lazy.assign(n*2, d0);
  }
  void build(vector<T> v) {
    REP(i, v.size()) dat[i+n-1] = v[i];
    for(int i=n-2; i>=0; --i) dat[i] = f(dat[i*2+1], dat[i*2+2]);
  }

  // 区間の幅がlenの節点kについて遅延評価
  inline void eval(int len, int k) {
    if(lazy[k] == d0) return;
    if(k*2+1 < n*2-1) {
      lazy[2*k+1] = h(lazy[k*2+1], lazy[k]);
      lazy[2*k+2] = h(lazy[k*2+2], lazy[k]);
    }
    dat[k] = g(dat[k],p(lazy[k],len));
    lazy[k] = d0;
  }
  // [a, b)
  T update(int a, int b, E x, int k, int l, int r) {
    eval(r-l, k);
    if(b <= l || r <= a) return dat[k];
    if(a <= l && r <= b) {
      lazy[k] = h(lazy[k], x);
      return g(dat[k], p(lazy[k],r-l));
    }
    return dat[k] = f(update(a, b, x, 2*k+1, l, (l+r)/2),
                      update(a, b, x, 2*k+2, (l+r)/2, r));
  }
  T update(int a, int b, E x) { return update(a, b, x, 0, 0, n); }
  // [a, b)
  T query(int a, int b, int k, int l, int r) {
    eval(r-l, k);
    if(a <= l && r <= b) return dat[k];
    bool left = !((l+r)/2 <= a || b <= l), right = !(r <= 1 || b <= (l+r)/2);
    if(left&&right) return f(query(a, b, 2*k+1, l, (l+r)/2), query(a, b, 2*k+2, (l+r)/2, r));
    if(left) return query(a, b, 2*k+1, l, (l+r)/2);
    return query(a, b, 2*k+2, (l+r)/2, r);
  }
  T query(int a, int b) { return query(a, b, 0, 0, n); }
};

signed main(void)
{
  int H, W, q;
  scanf("%lld%lld%lld", &H, &W, &q);
  VI a(H), b(W);
  REP(i, H) scanf("%lld", &a[i]);
  REP(i, W) scanf("%lld", &b[i]);

  // セグ木を定義
  auto f = [](VI a, VI b) {
    // max,minの数
    int maxnum = a[0]==max(a[0],b[0])?a[1]:0;
    maxnum += b[0]==max(a[0],b[0])?b[1]:0;
    int minnum = a[2]==min(a[2],b[2])?a[3]:0;
    minnum += b[2]==min(a[2],b[2])?b[3]:0;
    return VI{
      max(a[0],b[0]),
      maxnum,
      min(a[2],b[2]),
      minnum
    };
  };
  auto g = [](VI a, int b) {
    return VI{
      a[0]+b,
      a[1],
      a[2]+b,
      a[3]
    };
  };
  auto h = [](int a, int b) {
    return a+b;
  };
  segtree<VI,int> seg1(H, f, g, h, {0,1,0,1}, 0),
                 seg2(W, f, g, h, {0,1,0,1}, 0);

  // 初期状態を設定
  VVI inita, initb;
  REP(i, H) inita.PB({a[i], 1, a[i], 1});
  REP(i, W) initb.PB({b[i], 1, b[i], 1});
  seg1.build(inita); seg2.build(initb);

  // クエリにこたえる
  REP(i, q) {
    int type;
    scanf("%lld", &type);
    if(type==1) {
      int a, b, v;
      scanf("%lld%lld%lld", &a, &b, &v);
      seg1.update(a-1, b, v);
    } else if(type==2) {
      int a, b, v;
      scanf("%lld%lld%lld", &a, &b, &v);
      seg2.update(a-1, b, v);
    } else if(type==4) {
      int ma = -LLINF, num = 0;
      int a, b, c, d;
      scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
      VI v1 = seg1.query(a-1, b);
      VI v2 = seg2.query(c-1, d);
      ma = max({
        v1[0]*v2[0],
        v1[2]*v2[2],
        v1[0]*v2[2],
        v1[2]*v2[0]
      });
      // maxが0のケース
      if(ma == 0) {
        int tmpa = 0, tmpb = 0;
        if(v1[0] == 0) tmpa = v1[1];
        if(v1[2] == 0) tmpa = v1[3];
        if(v2[0] == 0) tmpb = v2[1];
        if(v2[2] == 0) tmpb = v2[3];
        num = (b-a+1)*(d-c+1) - (b-a+1-tmpa)*(d-c+1-tmpb);
      } else {
        if(v1[0] == v1[2] && v2[0] == v2[2]) {
          num = (b-a+1) * (d-c+1);
        } else if(v1[0] == v1[2] || v2[0] == v2[2]) {
          if(v1[2]*v2[2] == ma) num += v1[3]*v2[3];
          if(v1[0]*v2[0] == ma) num += v1[1]*v2[1];
        } else {
          if(v1[0]*v2[0] == ma) num += v1[1]*v2[1];
          if(v1[0]*v2[2] == ma) num += v1[1]*v2[3];
          if(v1[2]*v2[0] == ma) num += v1[3]*v2[1];
          if(v1[2]*v2[2] == ma) num += v1[3]*v2[3];
        }
      }
      printf("%lld %lld\n", ma, num);
    } else if(type==3) {
      int mi = LLINF, num = 0;
      int a, b, c, d;
      scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
      VI v1 = seg1.query(a-1, b);
      VI v2 = seg2.query(c-1, d);
      mi = min({
        v1[0]*v2[0],
        v1[2]*v2[2],
        v1[0]*v2[2],
        v1[2]*v2[0]
      });
      // minが0の場合
      if(mi == 0) {
        int tmpa = 0, tmpb = 0;
        if(v1[0] == 0) tmpa = v1[1];
        if(v1[2] == 0) tmpa = v1[3];
        if(v2[0] == 0) tmpb = v2[1];
        if(v2[2] == 0) tmpb = v2[3];
        num = (b-a+1)*(d-c+1) - (b-a+1-tmpa)*(d-c+1-tmpb);
      } else {
        if(v1[0] == v1[2] && v2[0] == v2[2]) {
          num = (b-a+1) * (d-c+1);
        } else if(v1[0] == v1[2] || v2[0] == v2[2]) {
          if(v1[2]*v2[2] == mi) num += v1[3]*v2[3];
          if(v1[0]*v2[0] == mi) num += v1[1]*v2[1];
        } else {
          if(v1[0]*v2[0] == mi) num += v1[1]*v2[1];
          if(v1[0]*v2[2] == mi) num += v1[1]*v2[3];
          if(v1[2]*v2[0] == mi) num += v1[3]*v2[1];
          if(v1[2]*v2[2] == mi) num += v1[3]*v2[3];
        }
      }
      printf("%lld %lld\n", mi, num);
    }
  }

  return 0;
}

codeforces #212 div2 D. Fools and Foolproof Roads

問題ページ Problem - D - Codeforces

概要

n頂点m辺の重み付き無向グラフが与えられる。このグラフに辺を張る操作をp回行うことで連結成分数がq個になるようにしたい。
辺を張るときは

  • 同じ連結成分内の頂点なら1000
  • 違う連結成分の頂点ならばmin(10^9, 連結成分の辺の重みの和)

のコストがかかる。
条件を満たす操作があるのか、そのうちかかるコストが最小となるような操作はどのようなものかを答えろ

考えたこと

  • 連結成分について扱うしunionfind?
  • unionfindで各連結成分について辺の重みの和をもっておくとよさそう
  • 最初の時点で連結成分数がqより少なければ不可能
  • 同じ連結成分内の頂点をつなぐ操作を違う連結成分の頂点をつなぐより優先すべきか考えると多分そんな状況はない
  • 違う連結成分の頂点をつなぐコストが増えるし連結成分数を減らすことはできないため
  • したがって連結成分数をq個にしたあとさらに操作をしないといけないときのみ同じ連結成分の頂点をつなげばよい
  • 逆に連結成分数がすでにq個で操作しないといけないのに2頂点以上の連結成分がなければ不可能
  • つなぐ連結成分はどうするべきか
  • 辺の重みの和が小さい連結成分から貪欲につなげばいい気持ちになったので貪欲を書くと通った

unionfindで連結成分、priority_queueで(辺の重みの和、どの連結成分か)について管理しながら貪欲
バグとコーナーに気をつけつつ実装する

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

class UnionFind {
public:
  const static int MAX_N = 100010;
  int par[MAX_N];
  int s[MAX_N];
  int w[MAX_N];
  UnionFind() { init(); }
  UnionFind(int n) { init(n); }
  void init() { for(int i=0; i<MAX_N; ++i) par[i] = i, s[i] = 1, w[i] = 0; }
  void init(int n) { for(int i=0; i<n; ++i) par[i] = i, s[i] = 1, w[i] = 0; }
  int find(int x) {
    if(par[x] == x) return x;
    return par[x] = find(par[x]);
  }
  void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if(x == y) return;
    if(s[x] < s[y]) par[x] = y, s[y] += s[x], w[y] += w[x];
    else par[y] = x, s[x] += s[y], w[x] += w[y];
  }
  bool same(int x, int y) { return find(x) == find(y);}
};
UnionFind uf;

VI v[100010];
signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  int n, m, p, q;
  cin >> n >> m >> p >> q;
  REP(i, m) {
    int x, y, l;
    cin >> x >> y >> l;
    x--, y--;
    uf.unite(x, y);
    uf.w[uf.find(x)] += l;
  }

  priority_queue<PII, vector<PII>, greater<PII>> que;
  vector<bool> used(n, false);
  int cnt = 0;
  REP(i, n) {
    if(used[uf.find(i)]) continue;
    used[uf.find(i)] = true;
    que.push({uf.w[uf.find(i)], uf.find(i)});
    cnt++;
  }

  if(cnt < q) {
    cout << "NO" << endl;
    return 0;
  }

  vector<PII> ans;
  int num = 0;
  while(num < p && cnt > q) {
    PII p1 = que.top(); que.pop();
    PII p2 = que.top(); que.pop();
    uf.unite(p1.second, p2.second);
    if(uf.w[uf.find(p1.second)] >= 1e9) {
      uf.w[uf.find(p1.second)] += 1e9;
    } else {
      uf.w[uf.find(p1.second)]*=2;
      uf.w[uf.find(p1.second)]++;
    }
    cnt--; num++;
    ans.PB({p1.second, p2.second});
    que.push({uf.w[uf.find(p1.second)], uf.find(p1.second)});
  }

  if(num == p) {
    if(cnt > q) {
      cout << "NO" << endl;
    } else if(cnt == q) {
      cout << "YES" << endl;
      for(auto i: ans) {
        cout << i.first+1 << " " << i.second+1 << endl;
      }
    }
    return 0;
  }

  REP(i, n) v[uf.find(i)].PB(i);
  int idx = -1;
  REP(i, n) if(v[i].size() >= 2) idx = i;
  if(idx == -1) {
    cout << "NO" << endl;
    return 0;
  }

  while(num < p) {
    ans.PB({v[idx][0], v[idx][1]});
    num++;
  }

  cout << "YES" << endl;
  for(auto i: ans) {
    cout << i.first+1 << " " << i.second+1 << endl;
  }

  return 0;
}

codeforces #212 div2 C. Insertion Sort

問題ページ
Problem - C - Codeforces

概要

n要素の順列が与えられる。この順列に挿入ソートを行う。
この順列のどこか2要素をswapすることで挿入ソートでの交換回数を最小化しろ。

考えたこと

  • ようするに転倒数を最小化すればいい
  • i,j (i<j)を交換したとき転倒数はどう変化するか
  • 区間[i+1,j-1]の数に対し影響する
    • a[i]より大きい数の個数の分swap回数がプラス
    • a[i]より小さい数の個数の分swap回数がマイナス
    • a[j]より大きい数の個数の分swap回数がマイナス
    • a[j]より小さい数の個数の分swap回数がプラス
  • a[i]<a[j] ならswap回数が+1、a[i]>a[j]なら-1
  • ある区間[l,r]でa[l]、a[r]より大きい数の個数を知りたい
  • 転倒数と同様にBITでやれば出来そうだけどO(n^2logn)は通らなさそうな制約
  • dp[l][r] = (区間[l,r]でa[l]より大きい数の個数) を前計算しておく
  • 区間をずらしつつ条件を満たす数の個数を数えるとO(n^2)で実装でき通る

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const int MOD = 1000000007;

int dp[5010][5010];
signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  int n;
  cin >> n;
  VI a(n);
  REP(i, n) cin >> a[i];

  int ret = n*(n-1)/2;
  REP(i, n) {
    FOR(j, i+1, n) {
      if(a[j] > a[i]) dp[i][j] = 1;
    }
    FOR(j, i+1, n) {
      dp[i][j] += dp[i][j-1];
    }
    ret -= dp[i][n-1];
  }

  int mi = INF, cnt = 1;
  REP(r, n) {
    int small = 0, big = 0;
    for(int l=r-1; l>=0; --l) {
      // vのうちa[l]より大きい数の個数
      int tmp = 0;
      tmp += dp[l][r];
      tmp -= r-l-1 - dp[l][r];
      // vのうちa[r]より大きい数の個数
      tmp += small;
      tmp -= big;
      if(a[l] > a[r]) tmp--;
      else tmp++;

      if(a[r] < a[l]) big++;
      else small++;
      if(mi > tmp) {
        mi = tmp;
        cnt = 1;
      } else if(mi == tmp) {
        cnt++;
      }
    }
  }
  cout << ret + mi << " " << cnt << endl;

  return 0;
}

AOJ2441 FizzBuzz

問題ページ
FizzBuzz | Aizu Online Judge

考えたこと

  • 数xまでの文字数が何文字になるか
  • Fizz,Buzzの部分は文字数が固定だからいいけど数字部分の文字数がずれるのがつらそう
  • ずれないように桁ごとに分ける
  • 1~9の中の3,5,15の倍数の数を数えると1~9のFizzBuzz stringの文字数がわかりそう
  • 他の桁数でも同様
  • したがって数xまでのFizzBuzz stringが何文字かはO(logx)で数えられる
  • こういう制約はどうせ周期性とかの規則があるか二分探索ができるかなので(は?)考えると二分探索ができそう
  • 数xまでの文字数がs以下か として二分探索をするとどの数からFizzBuzz stringをつくればいいのかがわかる
  • 頑張って実装すると通った

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)

int len[30];
signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  int s;
  cin >> s;

  int sum = 0;
  auto check = [&](int m) -> bool {
    sum = 0;
    int i, l;
    for(i=1, l=1; i<=m; i*=10, ++l) {
      // [i, 10*i)
      int a = (10*i-1)/3 - (i-1)/3;
      int b = (10*i-1)/5 - (i-1)/5;
      int c = (10*i-1)/15 - (i-1)/15;
      a -= c, b -= c;
      sum += (a + b + 2*c) * 4 + (10*i-i-a-b-c) * l;
    }
    // (m, i) を引く
    if(m < i) {
      int a = (i-1)/3 - m/3;
      int b = (i-1)/5 - m/5;
      int c = (i-1)/15 - m/15;
      a -= c, b -= c;
      sum -= (a + b + 2*c) * 4 + (i-1-m-a-b-c) * (l-1);
    }
    return sum < s;
  };

  int lb = 0, ub = 1e18;
  while(ub-lb>1) {
    int mid = (lb+ub)/2;
    if(check(mid)) {
      lb = mid;
    } else {
      ub = mid;
    }
  }
  // lb+1 の s - sum番目からのFizzBuzz string
  check(lb);

  int idx = lb+1;
  string ans = "";
  if(idx%15 == 0) {
    string tmp = "FizzBuzz";
    ans += tmp.substr(s-sum-1);
  } else if(idx%3==0) {
    string tmp = "Fizz";
    ans += tmp.substr(s-sum-1);
  } else if(idx%5==0) {
    string tmp = "Buzz";
    ans += tmp.substr(s-sum-1);
  } else {
    string tmp = to_string(idx);
    ans += tmp.substr(s-sum-1);
  }
  idx++;
  while(ans.size() < 20) {
    if(idx%15 == 0) {
      string tmp = "FizzBuzz";
      if(ans.size() + 8 <= 20) {
        ans += tmp;
      } else {
        ans += tmp.substr(0, 20-ans.size());
      }
    } else if(idx%3==0) {
      string tmp = "Fizz";
      if(ans.size() + 4 <= 20) {
        ans += tmp;
      } else {
        ans += tmp.substr(0, 20-ans.size());
      }
    } else if(idx%5==0) {
      string tmp = "Buzz";
      if(ans.size() + 4 <= 20) {
        ans += tmp;
      } else {
        ans += tmp.substr(0, 20-ans.size());
      }
    } else {
      string tmp = to_string(idx);
      if(ans.size() + tmp.size() <= 20) {
        ans += tmp;
      } else {
        ans += tmp.substr(0, 20-ans.size());
      }
    }
    idx++;
  }
  cout << ans.substr(0, 20) << endl;

  return 0;
}

AOJ2009 Area Separation

問題ページ
Area Separation | Aizu Online Judge

考えたこと

  • ある線を引く時、既に引いている線分との(交点の数+1)個領域が増える
  • ただし一つの交点に3本以上の線分が交差している場合、重複して数えてはいけない
  • 正方形の辺上で交わる場合を交差に数えてはいけないのを抜かしていて時間を溶かした

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define PB push_back

const double EPS = 1e-10;

using R = long double;
using P = complex<R>;
using L = pair<P,P>;
struct S : public L {
  S() {}
  S(const P &a, const P &b) : L(a,b) {}
};

inline int sgn(const R& r) { return (r>EPS) - (r<-EPS); }
inline R det(const P& a, const P& b) {
  return real(a)*imag(b) - imag(a)*real(b);
}
inline P vec(const L& l) {return l.second - l.first;}
namespace std {
    bool operator < (const P& a, const P& b) {
        return sgn(real(a-b)) ? real(a-b) < 0 : sgn(imag(a-b)) < 0;
    }
    bool operator == (const P& a, const P& b) {
        return sgn(real(a-b)) == 0 && sgn(imag(a-b)) == 0;
    }
}

// P,L,Sについて入力
inline istream& operator>>(istream& is, P& p) {
  R x, y;
  is >> x >> y;
  p = P(x, y);
  return is;
}
inline istream& operator>>(istream& is, L& l) {
  P a, b;
  is >> a >> b;
  l = L(a, b);
  return is;
}
inline istream& operator>>(istream& is, S& s) {
  P a, b;
  is >> a >> b;
  s = S(a, b);
  return is;
}

enum CCW{LEFT=1, RIGHT=2, BACK=4, FRONT=8, ON_SEG=16};
int ccw(P a, P b, P c) {
    P p = (c-a)/(b-a);
    if(sgn(imag(p)) > 0) return LEFT;
    if(sgn(imag(p)) < 0) return RIGHT;
    if(sgn(real(p)) < 0) return BACK;
    if(sgn(real(p)-1) > 0) return FRONT;
    return ON_SEG;
}

template<bool strict=false> inline bool intersect(const S&s1, const S&s2) {
  int ccw1 = ccw(s1.first, s1.second, s2.first) | ccw(s1.first, s1.second, s2.second);
  int ccw2 = ccw(s2.first, s2.second, s1.first) | ccw(s2.first, s2.second, s1.second);
  if(strict) return (ccw1 & ccw2) == (LEFT | RIGHT);
  return (ccw1 & ccw2) == (LEFT | RIGHT) || ((ccw1 | ccw2) & ON_SEG);
}

inline P crosspoint(const L& l1, const L& l2) {
  R ratio = det(vec(l2), l2.first-l1.first)/det(vec(l2),vec(l1));
  return l1.first + vec(l1)*ratio;
}

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  while(true) {
    int n;
    cin >> n;
    if(!n) break;
    S s[105];
    REP(i, n) cin >> s[i];

    int ret = 2;
    FOR(i, 1, n) {
      vector<P> v;
      int tmp = 0;
      REP(j, i) {
        if(!intersect<true>(s[i], s[j])) continue;
        P c = crosspoint(s[i], s[j]);
        bool flag = true;
        for(auto k: v) {
          if(k == c) {
            flag = false;
            break;
          }
        }
        if(flag) {
          v.PB(c);
          tmp++;
        }
      }
      ret += tmp + 1;
    }

    cout << ret << endl;
  }

  return 0;
}

AOJ1176 輪番停電計画

問題ページ Planning Rolling Blackouts | Aizu Online Judge

解法

func(sx,sy,gx,gy) = (指定範囲で実現できる最大のグループ数, グループの需要のうち最大) とする。その領域を分割しないか、縦に分割するか、横に分割するかの3通りの遷移がある。
分割しない場合はグループ数が1、需要の最大はその範囲の値の合計となる。ある矩形範囲の値の合計を求めるのには累積和を用いることでO(1)でできる。
ある二つの領域に分割したときは(グループ数の和,グループの需要の最大のうち小さい方)がその分割をマージした結果となる。
分割の方法は縦にO(W)通り、横にO(H)通り存在するので遷移がO(H+W)通り存在する。状態数がO((HW)^2)で遷移がO(H+W)なので32^5程度なので通る。

ソースコード

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using VI = vector<int>;
using VVI = vector<VI>;
using PII = pair<int, int>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define PB push_back

const int INF = (1LL<<30);

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

const int maxh = 35, maxw = 35;
int csum[maxh][maxw];
void init(VVI vec) {
  assert(vec.size());
  REP(i, vec.size()) REP(j, vec[0].size()) {
    if(!i && !j) csum[i][j] = vec[i][j];
    else if(!i) csum[i][j] = csum[i][j-1] + vec[i][j];
    else if(!j) csum[i][j] = csum[i-1][j] + vec[i][j];
    else csum[i][j] = csum[i-1][j] + csum[i][j-1] - csum[i-1][j-1] + vec[i][j];
  }
}
// 閉区間, 0-indexedで矩形範囲の合計
int cumsum(int sx, int sy, int gx, int gy) {
  if(!sx && !sy) return csum[gy][gx];
  if(!sx) return csum[gy][gx] - csum[sy-1][gx];
  if(!sy) return csum[gy][gx] - csum[gy][sx-1];
  return csum[gy][gx] - csum[gy][sx-1] - csum[sy-1][gx] + csum[sy-1][sx-1];
}

int h, w, s;
PII dp[35][35][35][35];
PII func(int sx, int sy, int gx, int gy) {
  if(dp[sx][sy][gx][gy] != PII{-1, -1}) return dp[sx][sy][gx][gy];
  // 分割しなかったとしても条件を満たさない
  if(cumsum(0, 0, w-1, h-1) - cumsum(sx, sy, gx, gy) > s) return {-1, -1};
  // 分割しない
  int group = 1, ret = cumsum(sx, sy, gx, gy);
  // 縦で割る
  FOR(i, sx, gx) {
    PII vl = func(sx, sy, i, gy);
    PII vr = func(i+1, sy, gx, gy);
    if(group < vl.first + vr.first || (group == vl.first+vr.first && ret < min(vl.second, vr.second))) {
      group = vl.first + vr.first;
      ret = min(vl.second, vr.second);
    }
  }
  // 横で割る
  FOR(i, sy, gy) {
    PII vl = func(sx, sy, gx, i);
    PII vr = func(sx, i+1, gx, gy);
    if(group < vl.first + vr.first || (group == vl.first+vr.first && ret < min(vl.second, vr.second))) {
      group = vl.first + vr.first;
      ret = min(vl.second, vr.second);
    }
  }
  return dp[sx][sy][gx][gy] = {group, ret};
}

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  while(true) {
    cin >> h >> w >> s;
    if(!h) break;
    VVI vec(h, VI(w));
    REP(i, h) REP(j, w) cin >> vec[i][j];

    // 累積和,dp配列の初期化
    init(vec);
    REP(i1, 35) REP(i2, 35) REP(i3, 35) REP(i4, 35) dp[i1][i2][i3][i4] = {-1, -1};

    PII ret = func(0, 0, w-1, h-1);
    cout << ret.first << " " << s - cumsum(0,0,w-1,h-1) + ret.second << endl;
  }

  return 0;
}