4653 - Array Game

4653 - Array Game - Asia - Tehran - 2009/2010
http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=4653

問題概要

長さ無限の1次元グリッドがある。

  • -300から300の間に+ or -の符号が乗っている。最大+,-100個ずつ
  • 同様の区間に数字がある。最大100個値の範囲は-9から9
  • この数字の集合全てを、正負の方向に1ずつ動かすことができる。すべての数字がうごく。
  • 符号の上に数字がのると、数字は消滅し、その数字の分スコアが減少or上昇する。符号は消滅しない。
  • 何回でも動かせる。いつでもやめられる。

実現できる最大スコアを求めよ。

解法

DP
最大右に動かした数 と 最大左に動かした数 が定まれば、消える数字が定まる。
それを状態にしてDP

最初前処理なにもせず、メモ化を適当にしたらTLE.
書き直した。
右or左に動かしたら、どの数字が消滅するかというのを前処理で求めておく。
その情報をもとにDPしたら間に合った。
でも、もっと早く解けてる人がいるから他に解法があるのだろう。

がんばって解けたからうれしい。
配列外参照ではまってたのがなんとも後悔。。。

ソース

#include<algorithm>
#include<cstdio>

#define REP(i,b,n) for(int i=b;i<(int)n;++i)
#define rep(i,n)   REP(i,0,n)
using namespace std;
const int INF = 1000000000;
const int N = 601;

int ope[N], pos[100], value[100], numN, numM, numP;
int dp[N][N];
int rightUsed[N][100];
int leftUsed[N][100];

void init(){
  rep(i, N){
    ope[i] = 0;
    rep(j, N){
      dp[i][j] = -INF;
    }
    rep(j, numN){
      rightUsed[i][j] = leftUsed[i][j] = 0;
    }
  }
  dp[0][0] = 0;
}

bool input(){
  scanf(" %d %d %d ", &numN, &numP, &numM);
  if(numN == 0 && numP == 0 && numM == 0)return false;
  init();
  rep(i, numN){
    scanf(" %d %d ", &pos[i], &value[i]);
    pos[i] += 300;
  }
  rep(i, numP){
    int p;
    scanf(" %d ", &p);
    ope[p+300] = 1;
  }
  rep(i, numM){
    int p;
    scanf(" %d ", &p);
    ope[p+300] = -1;
  }
  return true;
}

void solveUsed(){
  REP(i, 1, N){
    rep(j, numN){
      if(rightUsed[i-1][j] != 0){
        rightUsed[i][j] = rightUsed[i-1][j];
        continue;
      }
      int nowPos = pos[j] + i;
      if(nowPos > N)continue;
      rightUsed[i][j] = ope[nowPos];
    }
  }
  REP(i, 1, N){
    rep(j, numN){
      if(leftUsed[i-1][j] != 0){
        leftUsed[i][j] = leftUsed[i-1][j];
        continue;
      }
      int nowPos = pos[j] - i;
      if(nowPos < 0)continue;
      if(ope[nowPos] != 0){
        leftUsed[i][j] = ope[nowPos];
      }
    }
  }
}

void solve(){
  int ans = 0;
  rep(left, N){
    rep(right, N){
      ans = max(dp[left][right], ans);
      int nextRightScore = dp[left][right];
      int nextLeftScore = dp[left][right];
      rep(i, numN){
        if(rightUsed[right][i] == 0 && leftUsed[left][i] == 0){
          if(right < N-1 && rightUsed[right+1][i] != 0){
            nextRightScore += rightUsed[right+1][i] * value[i];
          }
          if(left < N-1 && leftUsed[left+1][i] != 0){
            nextLeftScore += leftUsed[left+1][i] * value[i];
          }
        }
      }
      if(right < N-1){
        dp[left][right+1] = max(dp[left][right+1], nextRightScore);
      }
      if(left < N-1){
        dp[left+1][right] = max(dp[left+1][right], nextLeftScore);
      }
    }
  }
  printf("%d\n", ans);
}

int main(){
  while(input()){
    solveUsed();
    solve();
  }
  return 0;
}

いつか、さらっと書けるようになりたい。