JOI 2010-2011 本選

結果がきたので安心して参加記がかける

1番

2次元累積和やるだけ。10分

#include <cstdio>

int m, n, q;
int js[1001][1001];
int os[1001][1001];
int is[1001][1001];

int main()
{
  char v[1005];
  scanf("%d%d%d", &m, &n, &q);
  for(int i=0;i<=m;i++)
    for(int j=0;j<=n;j++) js[i][j] = os[i][j] = is[i][j] = 0;
  for(int i=1;i<=m;i++){
    scanf("%s", v);
    for(int j=1;j<=n;j++){
      if(v[j-1]=='J') js[i][j]++;
      if(v[j-1]=='O') os[i][j]++;
      if(v[j-1]=='I') is[i][j]++;
    }
  }
  for(int i=0;i<=m;i++){
    for(int j=1;j<=n;j++){
      js[i][j] += js[i][j-1];
      os[i][j] += os[i][j-1];
      is[i][j] += is[i][j-1];
    }
  }
  for(int i=1;i<=m;i++){
    for(int j=0;j<=n;j++){
      js[i][j] += js[i-1][j];
      os[i][j] += os[i-1][j];
      is[i][j] += is[i-1][j];
    }
  }

  int a, b, c, d;
  for(;q--;){
    scanf("%d%d%d%d", &a, &b, &c, &d);
    a--; b--;
    printf("%d %d %d\n",
	   js[c][d] - js[a][d] - js[c][b] + js[a][b],
	   os[c][d] - os[a][d] - os[c][b] + os[a][b],
	   is[c][d] - is[a][d] - is[c][b] + is[a][b]);
  }

  return 0;
}

2番

ナップザックっぽいDPやるだけ。10分

#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> books[10];
vector<int> price[10];
int N, K;
int dp[2][2000];

int main()
{
  int c, g;
  scanf("%d%d", &N, &K);
  for(int i=0;i<N;i++){
    scanf("%d%d", &c, &g);
    books[--g].push_back(c);
  }
  for(int i=0;i<10;i++){
    sort(books[i].begin(), books[i].end());
    reverse(books[i].begin(), books[i].end());
    int sum = 0;
    price[i].push_back(0);
    for(int j=0;j<books[i].size();j++){
      sum += books[i][j];
      price[i].push_back(sum + j*(j+1));
    }
  }

  int t = 0;
  for(int i=0;i<=K;i++){
    dp[t][i] = dp[1-t][i] = 0;
  }
  for(int i=0;i<10;i++){
    for(int j=0;j<=K;j++) dp[1-t][j] = 0;
    for(int j=0;j<price[i].size();j++){ //sell j books that series is i
      for(int k=j;k<=K;k++){
        dp[1-t][k] = max(dp[1-t][k], dp[t][k-j]+price[i][j]);
      }
    }
    t = 1-t;
  }
  printf("%d\n", dp[t][K]);
  return 0;
}

3番

Dijkstraやるだけ。10分

#include <cstdio>
#include <vector>

using namespace std;

int N, M, K;
int dist[3000];
vector<int> to[3000], len[3000];
bool visited[3000];

int main()
{
  int a, b, l;

  scanf("%d%d%d", &N, &M, &K);
  for(int i=0;i<M;i++){
    scanf("%d%d%d", &a, &b, &l);
    --a; --b;
    to[a].push_back(b);
    to[b].push_back(a);
    len[a].push_back(l);
    len[b].push_back(l);
  }
  for(int i=0;i<N;i++){
    dist[i] = 1000000000;
    visited[i] = false;
  }

  for(;K--;){
    scanf("%d", &a);
    dist[--a] = 0;
  }
  for(int i=0;i<N;i++){
    int bp = -1;
    for(int j=0;j<N;j++){
      if(!visited[j] && (bp==-1 || dist[bp] > dist[j])) bp = j;
    }
    visited[bp] = true;
    for(int j=0;j<to[bp].size();j++){
      int t = to[bp][j], nl = dist[bp] + len[bp][j];
      if(dist[t] > nl) dist[t] = nl;
    }
  }

  int ret = 0;
  for(int i=0;i<N;i++){
    for(int j=0;j<to[i].size();j++) ret = max(ret, dist[i] + dist[to[i][j]] + len[i][j]);
  }
  printf("%d\n", (ret+1)/2);
  return 0;
}

4番

1〜3と「比べると」急に難しくなる。
終点を固定した場合は、X方向、Y方向ごとに、「中央値から始めると最良だよの法則」を用いて、(終点以外の点を倍にして)最良値がわかる。
終点を動かしても、いろいろがんばると答えが出る。
このコードはO(N log N)の中でも頭悪いので、各終点について最良値をSegment Treeを使って求めてる

#include <cstdio>
#include <algorithm>

using namespace std;

struct seg_tree
{
  long long *data[25];
  int size[25], len;

  seg_tree(int N)
  {
    len = 0;
    size[len++] = N;
    while(size[len-1] > 1){
      size[len] = (size[len-1]+1)/2;
      len++;
    }
    for(int i=0;i<len;i++){
      int l = (size[i]+1)/2*2;
      data[i] = new long long[l];
      for(int j=0;j<l;j++) data[i][j] = 0;
    }
  }

  void add(int P, int V)
  {
    for(int i=0;i<len;i++){
      data[i][P] += V;
      P >>= 1;
    }
  }

  void set(int P, int V)
  {
    data[0][P] = V;
    P >>= 1;
    for(int i=1;i<len;i++){
      data[i][P] = data[i-1][P*2] + data[i-1][P*2+1];
      P >>= 1;
    }
  }

  long long total(int left, int right)
  {
    long long ret = 0;
    for(int i=0;left!=right;i++){
      if(left&1){
        ret += data[i][left++];
      }
      if(right&1){
        ret += data[i][--right];
      }
      left >>= 1;
      right >>= 1;
    }
    return ret;
  }

  int ith(long long v)
  {
    //find smallest x that total(0, x) >= v
    int ret = 0;
    for(int i=len-1;i>0;i--){
      if(data[i-1][ret*2] < v){
        v -= data[i-1][ret*2];
        ret = (ret*2)+1;
      }else{
        ret *= 2;
      }
    }
    return ret;
  }
};

int N, X[100000], Y[100000];
int xs[100000], ys[100000];

int main()
{
  scanf("%*d%*d%d", &N);
  for(int i=0;i<N;i++){
    scanf("%d%d", X+i, Y+i);
    xs[i] = X[i];
    ys[i] = Y[i];
  }

  sort(xs, xs+N);
  sort(ys, ys+N);

  seg_tree sumX(2*N), sumY(2*N);
  for(int i=0;i<N;i++){
    sumX.set(i*2, xs[i]);
    sumX.set(i*2+1, xs[i]);

    sumY.set(i*2, ys[i]);
    sumY.set(i*2+1, ys[i]);
  }

  long long ret = 1LL << 62LL;
  int bx=0, by=0;
  for(int i=0;i<N;i++){
    long long tmp = 0;
    //prepare
    int xsp = lower_bound(xs, xs+N, X[i]) - xs;
    int ysp = lower_bound(ys, ys+N, Y[i]) - ys;

    //erase
    sumX.set(xsp*2, 0);
    sumY.set(ysp*2, 0);

    int mx = (xsp*2<N)?N:(N-1);
    int my = (ysp*2<N)?N:(N-1);

    tmp += (long long)xs[mx/2] * mx - sumX.total(0, mx);
    if(xsp*2 < mx) tmp -= xs[mx/2];
    tmp += sumX.total(mx+1, 2*N) - (long long)xs[mx/2] * (2*N - mx - 1);
    if(xsp*2 > mx) tmp += xs[mx/2];
    tmp += (long long)ys[my/2] * my - sumY.total(0, my);
    if(ysp*2 < my) tmp -= ys[my/2];
    tmp += sumY.total(my+1, 2*N) - (long long)ys[my/2] * (2*N - my - 1);
    if(ysp*2 > my) tmp += ys[my/2];
    //restore
    sumX.set(xsp*2, X[i]);
    sumY.set(ysp*2, Y[i]);

    //ret = min(ret, tmp);
    if(ret > tmp){
      ret = tmp;
      bx = xs[mx/2];
      by = ys[my/2];
    }
    if(ret == tmp){
      if(bx > xs[mx/2]){
        bx = xs[mx/2];
        by = ys[my/2];
      }else if(bx == xs[mx/2] && by > ys[my/2]){
        by = ys[my/2];
      }
    }
  }

  printf("%lld\n%d %d\n", ret, bx, by);
  return 0;
}

5番

二分探索×Segment Tree。これも1〜3に比べると難しいが似たような問題がIOI2009「Hiring」にあったような
二分探索で答えを固定し(例えばKとする)、許容量の大きいほうからK個をまず放出量setに追加し、上限を下げるときには下げたやつを使って放出量setを更新したり更新しなかったりする。setは実際にはSegment Treeを使って書いてる。

#include <cstdio>
#include <algorithm>

using namespace std;

struct bug
{
  int out, able;
};

struct seg_tree
{
  int *data[25], size[25], len;

  seg_tree(int N)
  {
    len = 0;
    size[len++] = N;
    while(size[len-1] > 1){
      size[len] = (size[len-1]+1)/2;
      len++;
    }
    for(int i=0;i<len;i++){
      int l = (size[i]+1)/2*2;
      data[i] = new int[l];
      for(int j=0;j<l;j++) data[i][j] = 0;
    }
  }

  ~seg_tree()
  {
    for(int i=0;i<len;i++) delete [] data[i];
  }

  void set(int p, int v)
  {
    data[0][p] = v;
    p >>= 1;
    for(int i=1;i<len;i++){
      data[i][p] = max(data[i-1][p*2], data[i-1][p*2+1]);
      p >>= 1;
    }
  }

  int max_pos()
  {
    int ret = 0;
    for(int i=len-1;i>0;i--){
      if(data[i][ret] == data[i-1][ret*2]) ret *= 2;
      else ret = ret*2+1;
    }

    return ret;
  }

  int get(int p)
  {
    return data[0][p];
  }

};

int N;
bug bg[300000];

inline bool operator<(const bug& a, const bug& b)
{
  return a.able > b.able;
}

bool test(int K)
{
  seg_tree seg(K); //minimal K

  long long sum = 0;
  for(int i=0;i<K-1;i++){
    seg.set(i, bg[i].out);
    sum += bg[i].out;
  }

  for(int i=K-1;i<N;i++){
    if(i == K-1){
      seg.set(K-1, bg[i].out);
      sum += bg[i].out;
    }else{
      int mp = seg.max_pos();
      if(seg.get(mp) > bg[i].out){
        sum -= seg.get(mp);
        sum += bg[i].out;
        seg.set(mp, bg[i].out);
      }
    }

    if(sum <= (long long)K * bg[i].able) return true;
  }

  return false;
}

int main()
{
  scanf("%d", &N);
  for(int i=0;i<N;i++) scanf("%d%d", &(bg[i].out), &(bg[i].able));
  sort(bg, bg+N);

  int left=0, right=N;
  while(left < right){
    if(right - left == 1){
      if(!test(right)) right = left;
      break;
    }
    int mid = (left + right) / 2;
    if(test(mid)){
      left = mid;
    }else{
      right = mid;
    }
  }

  printf("%d\n", right);

  return 0;
}

結果

3番でDijkstraをO(N^2+M)で書いたり、5番でO(N*(logN)^2)で解いたりしてTLEが怖かった。ほかにもバグが怖かった。
結果20+20+20+20+20=100で満点だったので満点詐欺は回避できました