2013年12月31日火曜日

AOJ0214 秋のイルミネーション

解法


四角形の交差判定が出来れば勝ち。
あとはUnionFind使っただけ。

四角形の交差判定は、
1.aがbの中にある
2.bがaの中にある
3.aの各辺がbの各辺でどれか交差する
こんな感じでやった。

ライブラリ作ったのでやってみたけど、ライブラリあるといい(小並感)

ソース


#include<iostream>
#include<complex>
#include<vector>
using namespace std;
typedef complex < double > P;
typedef vector< P > G;
vector< G > g;
const double EPS = 1e-8;
const double INF = 1e12;
double cross(const P& a,const P& b){ //外積
  return imag(conj(a) * b);
}
double dot(const P& a,const P& b){ // 内積
  return real(conj(a) * b);
}
struct L: vector < P >{
  L(const P& a,const P& b){
    push_back(a);
    push_back(b);
  }
};
int ccw(P a,P b,P c){
  b -= a; c -= a;
  if(cross(b,c) > 0) return 1;
  if(cross(b,c) < 0) return -1;
  if(dot(b,c) < 0) return 2; // c--a--b
  if(norm(b) < norm(c)) return -2; // a--b--c
  return 0; // a--c--b
}
bool intersectSS(const L& s,const L& t){ //交差判定 線分:線分
  return ccw(s[0],s[1],t[0]) * ccw(s[0],s[1],t[1]) <= 0 &&
    ccw(t[0],t[1],s[0]) * ccw(t[0],t[1],s[1]) <= 0;
}
#define curr(P,i) P[i]
#define next(P,i) P[(i+1)%P.size()]
bool conteins(const G& Q, const P& p){
  bool in = false;
  for(int i = 0 ; i < Q.size() ; i++ ){
    P a = curr(Q,i) - p, b = next(Q,i) - p;
    if(imag(a) > imag(b)) swap(a,b);
    if(imag(a) <= 0 && 0 < imag(b) && cross(a,b) < 0) in = !in;
    if(cross(a,b) == 0 && dot(a,b) <= 0) return true;
  }
  return in;
}
bool intersect( int x, int y){ //交差判定 g[x] == g[y]
  // g[x] or g[y] が包容
  for(int i = 0 ; i < 4 ; i++ ){
    if(conteins(g[y],g[x][i]) || conteins(g[x],g[y][i])) return true;
  }
  // g[x] と g[y]の辺が交差
  for(int i = 0 ; i < 4 ; i++ ){
    for(int j = 0 ; j < 4 ; j++ ){
      if(intersectSS(L(curr(g[x],i),next(g[x],i)),L(curr(g[y],j),next(g[y],j)))) return true;
    }
  }
  return false;
}
//UnionFind
vector< int > rank, p;
void link(int x,int y){
  if(rank[x] > rank[y]) p[y] = x;
  else{
    p[x] = y;
    if(rank[x] == rank[y]) rank[y]++;
  }
}
void init(int size){
  rank.resize(size);
  p.resize(size);
  for(int i = 0 ; i < size ; i++ ){
    p[i] = i, rank[i] = 0;
  }
}
int findSet(int x){
  if(x != p[x]) p[x] = findSet(p[x]);
  return p[x];
}
void Union(int x,int y){
  link(findSet(x),findSet(y));
}
bool isSame(int x,int y){
  return findSet(x) == findSet(y);
}

int main(){
  int N, M;
  while(cin >> N , N){
    for(int i = 0 ; i < N ; i++ ){
      cin >> M;
      g.resize(M);
      init(M);
      for(int j = 0 ; j < M ; j++ ){
        for(int k = 0 ; k < 4 ; k++ ){
          P buff;
          cin >> buff.real() >> buff.imag();
          g[j].push_back(buff);
        }
      }
      for(int j = 0 ; j < M ; j++ ){
        for(int k = 0 ; k < j ; k++ ){
          if(!isSame( j, k) && intersect( j, k)) Union( j, k);
        }
      }
      int ret = 0;
      for(int j = 0 ; j < M ; j++ ){
        if(findSet(j) == j) ret++;
      }
      cout << ret << endl;
      g.clear();
    }
  }
}

2013年12月26日木曜日

AOJ0213 土地分割

たぶん2013年最後の更新。

解法


DFSにパターン数を足したような再帰すると良い。
枝狩りは、『面積 % 高さ != 0』のときと『すでに誰かの土地がある』とこらへん。
なんかうまく全探できなかったので修行が必要かも。あんまり変数をグローバル化しすぎると良くないね(確信)
計算量が怪しかったが普通に良かった。

ソース


#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<cstring>
using namespace std;
#define fr first
#define sc second
typedef pair < int , int > Pi;
typedef pair < int , Pi > Pii;
  
int h, w, n, ans[10][10];
vector< Pii > memo;
  
int wy, wx;
void plus_n( int y, int x, int nt){
  wy = y;
  wx = ( x + nt ) % w;
  if(!wx) wy++;
  return ;
}
 
int dfs( const int mas[][10], const int used, const int y, const int x){
  if( y == h){ //exit
    memcpy( ans, mas, sizeof(int)*100);
    return 1;
  }
  int ret = 0;
  if( mas[y][x] != 0){
    plus_n( y, x, 1);
    ret += dfs( mas, used, wy, wx);
  }else{
    for(int bit = 0 ; bit < n ; bit++ ){
      if( (used >> bit) & 1 ) continue; // もうやってある
  
      for(int i = 1 ; i <= memo[bit].fr ; i++ ){ //高さ決定
        if( memo[bit].fr % i != 0 ) continue; // 長方形無理
        int height = i, width = memo[bit].fr / i;
  
        if( y <= memo[bit].sc.fr && y > memo[bit].sc.fr - height &&
            x <= memo[bit].sc.sc && x > memo[bit].sc.sc - width &&
            y + height - 1 < h && x + width - 1 < w){
          int java[10][10];
          bool ok = true;
          memcpy( java, mas, sizeof(int)*100);
          for(int j = 0 ; j < height ; j++ ){
            for(int k = 0 ; k < width ; k++ ){
              if(java[j + y][k + x]) ok = false;
              java[j + y][k + x] = bit + 1;
            }
          }
          if(ok){
            plus_n( y, x, width);
            ret += dfs( java, used|(1<<bit), wy, wx);
          }
        }
      }
    }
  }
  return ret;
}
int main(){
  while(cin >> w >> h >> n , w){
    memo.resize( n);
    for(int i = 0, bf ; i < n ; i++ ){
      cin >> bf;
      cin >> memo[bf - 1].fr;
    }
    for(int i = 0, c ; i < h ; i++ ){
      for(int j = 0 ; j < w ; j++ ){
        cin >> c;
        if(c)memo[c - 1].sc = Pi( i, j);
      }
    }
    int mas[10][10] = {{}};
    if(dfs( mas, 0, 0, 0) == 1){
      for(int i = 0 ; i < h ; i++ ){
        for(int j = 0 ; j < w ; j++ ){
          cout << ( j ? " " : "" ) << ans[i][j];
        }
        cout << endl;
      }
    }else cout << "NA" << endl;
  }
}

2013年12月23日月曜日

JOI予選2009

レシート


コンパイルせずに通したら1回TLE食らった。ループが10回になっていた。
サンプルは一応通そう(いましめ)
#include<iostream>

using namespace std;

int main(){
  int sum, in;
  while( cin >> sum, sum){
    for(int i = 0 ; i < 9 ; i++ ){
      cin >> in;
      sum -= in;
    }
    cout << sum << endl;
  }
}


すごろく


落ち着いて実装するだけ。
#include<iostream>
#include<algorithm>
 
using namespace std;
 
int main(){
  int n, m, mas[1001];
  while( cin >> n >> m, n){
    for(int i = 1 ; i <= n ; i++ ){
      cin >> mas[i];
    }
    int ret = -1;
    for(int i = 1, now = 1, MLE ; i <= m ; i++ ){
      cin >> MLE;
      now = min( now + MLE, n);
      if( !~ret && now == n) ret = i;
      now = min( now + mas[now], n);
      if( !~ret && now == n) ret = i;
    }
    cout << ret << endl;
  }
}

パーティー


実装楽そうなわーしゃるふろいどを使った。
ただしこれだと実行速度がアレ。
#include<iostream>
#include<algorithm>

using namespace std;

#define INF ( 1 << 15 )

int main(){
  int n, m, mas[501][501];
  while( cin >> n >> m , n){
    fill_n( *mas, 501 * 501, INF);
    for(int i = 0, a, b ; i < m ; i++ ){
      cin >> a >> b;
      mas[a][b] = mas[b][a] = 1;
    }
    for(int i = 1 ; i <= n ; i++ ){
      for(int j = 1 ; j <= n ; j++ ){
        for(int k = 1 ; k <= n ; k++ ){
          mas[j][k] = min( mas[j][k], mas[j][i] + mas[i][k]);
        }
      }
    }
    int ret = 0;
    for(int i = 2 ; i <= n ; i++ ){
      if( !~(mas[1][i]-2) | !(mas[1][i]-2)) ret++;
    }
    cout << ret << endl;
  }
}

カード並べ


さっきからバグらせすぎなんだが(震え声) 眠いからかな?
bitで使ったカードを保存しながらsetに突っ込む。
#include<iostream>
#include<algorithm>
#include<string>
#include<set>

using namespace std;

#define INF ( 1 << 15 )

string array[10];
int n, k;
static set < string > ret;

void rec( string s, int bit, int cnt){
  if(cnt == k){
    ret.insert( s);
    return;
  }
  for(int i = 0 ; i < n ; i++ ){
    if((bit >> i) & 1) rec( s + array[i], bit&~(1<<i), cnt + 1);
  }
}
int main(){
  while(cin >> n >> k , n){
    for(int i = 0 ; i < n ; i++ ){
      cin >> array[i];
    }
    rec( "", (1 << n) - 1, 0);
    cout << ret.size() << endl;
    ret.clear();
  }
}

通勤経路


頑張って場合分けしてメモ化再帰。
多分以下の5つに場合分け出来る。
① スタート地点( 次どっちも行けて、その次どっちも曲がれる)
② 上上で来た( 次どっちも行けて、右に曲がった場合その次曲がれない)
③ 右右で来た( 次どっちも行けて、上に曲がった場合その次曲がれない)
④ 右上で来た( 次は上しか行けないけど、その次どっちも曲がれる)
⑤ 上右で来た( 次は右しか行けないけど、その次どっちも曲がれる)

#include<iostream>
#include<algorithm>

using namespace std;

const int MOD = 100000;
int w, h, dp[100][100][4];

int rec( int y, int x, int j){
  if( y >= h || x >= w) return 0;
  if( ~dp[y][x][j]) return dp[y][x][j];
  if( y == h - 1 && x == w - 1) return 1;

  int ret;
  if(!y && !x) ret = rec( y + 1, x, 0) + rec( y, x + 1, 1);
  else if( j == 0 ) ret = rec( y + 1, x, 0) + rec( y, x + 1, 3);
  else if( j == 1 ) ret = rec( y + 1, x, 2) + rec( y, x + 1, 1);
  else if( j == 2 ) ret = rec( y + 1, x, 0);
  else if( j == 3 ) ret = rec( y, x + 1, 1);

  return dp[y][x][j] = ret % MOD;
}
int main(){
  while( cin >> w >> h, w){
    fill_n( **dp, 100 * 100 * 4, -1);
    cout << rec( 0, 0, 0) << endl;
  }
}

方向音痴のトナカイ


眠いので前やったソース。もうちょっと早い解法があるのでまた今度やる。
普通にDFSやってるぽい(適当)
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
 
using namespace std;
 
typedef pair<int,int> Pt;
#define fr first
#define sc second
 
int mas[10][10],h,w,dx[]={0,1,-1,0},dy[]={1,0,0,-1};
Pt gl;
Pt Move(Pt p,int n){
  while(true){
    p.fr += dx[n] , p.sc += dy[n];
    if(p.fr >= 0 && p.fr < h && p.sc >= 0 && p.sc < w){
      if( mas[p.fr][p.sc] == 1 ) return p;
    }else return Pt(-1,-1);
  }
}
int solve(Pt p,int home){
  if( !home ){
    return ( p.fr == gl.fr || p.sc == gl.sc ? 1 : 0 );
  }
  int rec = 0;
  for(int i=0;i<4;i++){
    Pt s = Move(p,i);
    if(s.fr == -1) continue;
    mas[s.fr][s.sc] = -1;
    rec += solve(s,home-1);
    mas[s.fr][s.sc] = 1;
  }
  return rec;
}
int main(){
  while(cin >> w >> h && w){
    int home = 0;
    for(int i=0;i<h;i++){
      for(int j=0;j<w;j++){
        cin >> mas[i][j];
        if( mas[i][j] == 1 ) home++;
        else if(mas[i][j] == 2 ) gl = Pt(i,j);
      }
    }
    cout << solve(gl,home) << endl;
  }
}

2013年12月22日日曜日

AOJ0191 Baby Tree

解法


自明なDP。
1回目に与えた肥料だけでは、苗木の大きさが変わりません。
2回目以降は、その回に与えた肥料と、その直前に与えた肥料との組み合わせによって苗木に 影響を与えます。良い影響を与えると苗木が伸び、悪い影響を与えると苗木が縮んでしまうこともあります。
ここからすべてを察した。

ソース


#include<iostream>
#include<iomanip>
#include<algorithm>
using namespace std;
double dp[100][100],g[100][100];
int main(){
  int n, m;
  while(cin >> n >> m , n){
    for(int i = 0 ; i < n ; i++ )
      for(int j = 0 ; j < n ; j++ ) cin >> g[i][j];
    fill_n( dp[0], n, 1.0);
    fill_n( dp[1], 9900, 0.0);
    for(int i = 1 ; i < m ; i++ ) //回
      for(int prev = 0 ; prev < n ; prev++ ) // prev
        for(int now = 0 ; now < n ; now++ ) // now
          dp[i][now] = max( dp[i][now], dp[i - 1][prev] * g[prev][now]);
 
    cout << fixed << setprecision(2) << *max_element( dp[m - 1], dp[m - 1] + n ) << endl;
  }
}

2013年12月18日水曜日

AOJ0210 ザ・スクエアーズ

解法


誤読していた。向きを変えるので1秒、動こうとするので1秒とかやって訳の分からないことをしていた。
向きを変えるステップは自明なので簡単だが、移動するステップが面倒。
最初人視点でやっていたので詰んでしまったが、マス視点でやってあとは頑張った。
dxとかdyとかdmとか4次元配列になってるけど、これはただ求める計算式に至らなかっただけ。
良い感じにシュミレーションすれば勝てる。

ソース


#include<iostream>
#include<vector>
#include<map>
#include<string>
using namespace std;
typedef pair < int , int > Pi;
typedef pair < int , Pi > Pii;
#define fr first
#define sc second
const int dy[][4] = {{1,0,-1,0},{0,-1,0,1},{-1,0,1,0},{0,1,0,-1}};
const int dx[][4] = {{0,1,0,-1},{1,0,-1,0},{0,-1,0,1},{-1,0,1,0}};
const int dm[][4] = {{3,0,1,2},{0,1,2,3},{1,2,3,0},{2,3,0,1}};
const int my[] = { 0, -1, 0, 1}, mx[] = { 1, 0, -1, 0};
const int md[] = { 2, 3, 0, 1};
int w, h;
char mas[30][30];
vector< Pii > vc;
string tmp = "ENWS";
int search( int y , int x){
  for(int i = 0 ; i < vc.size() ; i++ ){
    if(Pi(y,x) == vc[i].sc){
      return i;
    }
  }
  return -1;
}
vector< Pii >  solve(){
  vector< Pii > java;
  for(int i = 0 ; i < vc.size() ; i++ ){
    //change
    for(int j = 0 ; j < 4 ; j++ ){
      int yy = vc[i].sc.fr + dy[vc[i].fr][j];
      int xx = vc[i].sc.sc + dx[vc[i].fr][j];
      if(mas[yy][xx] != '#' && search(yy,xx) == -1){
        vc[i].fr = dm[vc[i].fr][j];
        break;
      }
    }
  }
  //push
  bool list[1000] = {};
  for(int i = 0 ; i < h ; i++ ){
    for(int j = 0 ; j < w ; j++ ){
      if(mas[i][j] == '#' || search(i,j) != -1) continue;
      for(int k = 0 ; k < 4 ; k++ ){
        int ny = i + my[k], nx = j + mx[k], iti;
        if((iti = search(ny,nx)) == -1) continue;
        if(vc[iti].fr == md[k]){
          if(mas[i][j] != 'X') java.push_back(Pii(vc[iti].fr,Pi(i,j)));
          list[iti] = true;
          break;
        }
      }
    }
  }
  for(int i = 0 ; i < vc.size() ; i++ ){
    if(!list[i]) java.push_back(vc[i]);
  }
  return java;
}
int main(){
  while(cin >> w >> h , w){
    for(int i = 0 ; i < h ; i++ ){
      for(int j = 0 ; j < w ; j++ ){
        cin >> mas[i][j];
        if(tmp.find(mas[i][j]) != string::npos){
          vc.push_back(Pii( tmp.find(mas[i][j]), Pi( i, j)));
        }
      }
    }
    int ret = 0;
    while(ret <= 180 && !vc.empty()){
      vc = solve();
      ret++;
    }
    if(ret == 181) cout << "NA" << endl;
    else cout << ret << endl;
    vc.clear();
  }
}

AOJ1155 如何に汝を満足せしめむ? いざ数え上げむ…

解法


最近構文解析にハマるワタクシ。
AOJ1244 Molecular FormulaとかAOJ2401 恒等式とか以前ブログに取り上げたと思う。
三値論理の式が与えられてホゲってしまう。
なんか論理積とか論理和とか否定とか考えれば出来そうだけど、面倒臭かったのでテーブル作った。

ソース


#include<iostream>
#include<string>
#include<cctype>
#include<algorithm>
using namespace std;
typedef string::const_iterator Cursol;
int P, Q, R;
int tmp1[3] = { 2, 1, 0}, tmp2[3][3] = {{0,0,0},{0,1,1},{0,1,2}};
int tmp3[3][3] = {{0,1,2},{1,1,2},{2,2,2}};
 
int formula(Cursol &c){
  int ret ;
  if(*c == 'P') ret = P;
  else if(*c == 'Q') ret = Q;
  else if(*c == 'R') ret = R;
  else if(isdigit(*c)) ret = *c - '0';
  else if(*c == '-') return tmp1[formula(++c)];
  else if(*c == '('){
    ret = formula(++c);
    if(*c == '*') ret = tmp2[ret][formula(++c)];
    else if(*c == '+') ret = tmp3[ret][formula(++c)];
  }
  c++;
  return ret;
}
int main(){
 
  string s;
  Cursol c;
 
  while( cin >> s, s != "."){
    int ret = 0;
    for(P = 0 ; P < 3 ; P++ ){
      for(Q = 0 ; Q < 3 ; Q++ ){
        for(R = 0 ; R < 3 ; R++ ){
          c = s.begin();
          if( 2 == formula(c)) ret++;
        }
      }
    }
    cout << ret << endl;
  }
}

AOJ0244 Bicycle Diet

解法


H,D,C,Lを適当に数字に置き換えて一つにまとめてしまう。
あとは既に行ったケーキ屋さんの情報をbitで持たせて幅優先するだけ。
微妙にバグったけど察して修正した。
最初ケーキ屋さんの数が100に見えて怖かった。問題文はちゃんと読もう(戒め)

ソース


#include<iostream>
#include<queue>
#include<vector>
#include<string>
#include<cstdlib>
using namespace std;
#define fr first
#define sc second
#define INF ( 1 << 30 )
int m, n, k, d;
int min_cost[110][1 << 6];
vector< int > cal;
typedef pair < int , int > Pi;
typedef pair < int , Pi > Pii;
struct edge{
  int to, cost;
  edge(){}
  edge( int to, int cost): to(to), cost(cost){};
};
vector < edge > info[110];
int java(string& s){
  if(s[0] == 'H') return 0; //MyHome
  if(s[0] == 'D') return 1; //市役所
  if(s[0] == 'C') return atoi(s.substr(1).c_str()) + 1; //ケーキ屋さん
  if(s[0] == 'L') return atoi(s.substr(1).c_str()) + m + 1; //ランドマーク
}
int bfs(){
  int ret = INF;
  fill_n(*min_cost,110*(1<<6),INF);
  queue< Pii > que;
  que.push(Pii( 0, Pi( 0, (1<<m) - 1)));
  min_cost[0][(1<<m)-1] = true;
  while(!que.empty()){
    Pii p = que.front();
    que.pop();
    if(p.sc.fr == 1) ret = min( ret, p.fr);
    if(min_cost[p.sc.fr][p.sc.sc] < p.fr ) continue;
    for(int i = 0 ; i < info[p.sc.fr].size() ; i++ ){
      edge e = info[p.sc.fr][i];
      int cost = p.fr + e.cost * k, bit = p.sc.sc;
      if( e.to >= 2 && e.to < 2 + m ){
        if((bit >> (e.to-2)) & 1){
          cost -= cal[e.to-2];
          bit &= ~(1 << (e.to-2));
        }else continue;
      }
      if( cost < min_cost[e.to][bit] ){
        que.push(Pii( cost,Pi( e.to, bit)));
        min_cost[e.to][bit] = cost;
      }
    }
  }
  return ret;
}
int main(){
  while(cin >> m >> n >> k >> d , m){
    cal.resize(m);
    for(int i = 0 ; i < m ; i++ ){
      cin >> cal[i];
    }
    for(int i = 0 ; i < d ; i++ ){
      string s1, s2;
      int cost;
      cin >> s1 >> s2 >> cost;
      info[java(s1)].push_back(edge( java(s2), cost));
      info[java(s2)].push_back(edge( java(s1), cost));
    }
    cout << bfs() << endl;
    for(int i = 0 ; i < 110 ; i++ ) info[i].clear();
  }
}

2013年12月16日月曜日

JOI予選2014 part2

5問目

普通にDijkstraやったら通ってしまったのでなぜこの問題をやらなかったのか後悔せざる得ない。
20分位で書けてしまってテストケースすべて1発で通してしまったので萎えた。
#include<iostream>
#include<queue>
#include<vector>
#include<map>
using namespace std;
#define fr first
#define sc second
typedef pair < int , int > Pi;
struct edge{
  int limit, cost;
  vector< int > node , cango;
} info[5001];
int n, k;
bool used[5001];

int Dijkstra(){
  priority_queue< Pi , vector< Pi > , greater< Pi > > que;
  for( que.push(Pi(0,0)) ; !que.empty() ; que.pop()){
    Pi p = que.top();
    if(used[p.sc]++) continue;
    if(p.sc == n - 1) return p.fr;
    for(int i = 0 ; i < info[p.sc].cango.size() ; i++ ){
      que.push(Pi(p.fr + info[p.sc].cost,info[p.sc].cango[i]));
    }
  }
}
int main(){
  cin >> n >> k;
  for(int i = 0 ; i < n ; i++ ){
    cin >> info[i].cost >> info[i].limit;
  }
  for(int i = 0, a, b ; i < k ; i++ ){
    cin >> a >> b;
    info[--a].node.push_back(--b);
    info[b].node.push_back(a);
  }

  queue< Pi > que;
  for(int i = 0 ; fill_n(used, 5001, false), i < n ; i++ ){
    for( que.push(Pi(i,info[i].limit)) ; !que.empty() ; que.pop() ){
      Pi p = que.front();
      if(!p.sc) continue;
      for(int j = 0 ; j < info[p.fr].node.size() ; j++ ){
        if(!used[info[p.fr].node[j]]++){
          info[i].cango.push_back(info[p.fr].node[j]);
          que.push(Pi(info[p.fr].node[j],p.sc-1));
        }
      }
    }
  }
  cout << Dijkstra() << endl;
}

2013年12月15日日曜日

JOI予選2014

感想

ああ...(察して
あえて言うなら逝ってしまった

問4

#include<iostream>
#include<string>
using namespace std;
#define MOD 10007
int n, dp[1000][1 << 3];
string s, tmp = "JOI";
int solve(int today, int bit){
  if( today == n ) return 1;
  if( dp[today][bit] ) return dp[today][bit];
  int ret = 0;
  for(int i = 0 ; i < (1 << 3) ; i++ ){
    if(( i >> tmp.find(s[today])) & 1 && i&bit){
      ret += solve( today + 1, i);
    }
  }
  return dp[today][bit] = ret % MOD;
}
int main(){
  cin >> n >> s;
  cout << solve( 0, 1) << endl;
}
自明なのによくわからないことをしてしまった(小並感)
if( today == n + 1 ) と固定されていた、また鍵を気にし過ぎたのがすべての敗因だったかもしれない。
あとほとんど一緒じゃね?って思ってみたり思ってみなかったり。
なんだか困惑と絶望だけが残ってしまった。
1年のうちに本戦行きたかったので留年したい(願望)

AOJ2401 恒等式

問題概要


与えられた等式が,すべての場合において成り立つかどうか判定する。
定数: T, F
変数: a, b, c, d, e, f, g, h, i, j, k
論理否定: -X
論理積: (X*Y)
論理和: (X+Y)
論理包含: (X->Y)

解法


構文解析する。変数が最大11個なのですべての場合において試した。

ソース


#include<iostream>
#include<string>
#include<map>
#include<sstream>
#include<cctype>
#include<algorithm>
using namespace std;
typedef string::const_iterator Cursol;
bool ok;
int bit;
bool Formula(Cursol &c){
  bool ret;
  if(*c == 'T') ret = true;
  else if(*c == 'F') ret = false;
  else if(islower(*c)) ret = bit >> ((*c) - 'a') & 1;
  else if(*c == '-') return !Formula(++c);
  else if(*c == '('){
    ret = Formula(++c);
    if(*c == '+'){
      ret |= Formula(++c);
    }else if(*c == '*'){
      ret &= Formula(++c);
    }else if(*c == '-'){ //->
      c++;
      ret = !ret | Formula(++c);
    }
  }
  c++;
  return ret;
}
int main(){
  string s;
  Cursol c;
  while(cin >> s , ok = true, s != "#"){
    string l = s.substr(0,s.find('='));
    string r = s.substr(s.find('=')+1);

    for(bit = 0 ; bit < (1 << 11) ; bit++ ){
      c = l.begin();
      bool java = Formula(c);
      c = r.begin();
      if( java != Formula(c) ){
 ok = false;
 break;
      }
    }
    if(ok) cout << "YES" << endl;
    else cout << "NO" << endl;
  }
}

AOJ1244 Molecular Formula

問題概要


元素記号と重さの対応表が最初に与えられる。
その後、化学式が与えられるので、その重さを答える。
未知の元素が含まれている場合は"UNKNOWN"と出力する。

<molecule>::=<atom>|<atom><number>|'('<molecule>')'<number>|<molecule><molecule>
<atom>::=<capitaletter>|<capitaletter><smallletter>
<number>::=2|3|4|・・・|97|98|99
<capitalletter>::=A|B|C|・・・|X|Y|Z
<smallletter>::=a|b|c|・・・|x|y|z

解法


BNFに従って構文解析するだけ。

ソース


#include<iostream>
#include<string>
#include<map>
#include<sstream>
#include<cctype>
using namespace std;
typedef string::const_iterator Cursol;
map < string , int > d;
bool ok;
int stoi(string s) {
  int v;
  stringstream ss(s);
  ss>>v;
  return v;
}
int Num(Cursol &c){
  int ret;
  string s;
  s += *c++;
  if(isdigit(*c)) s += *c++;
  return stoi(s);
}
int Atom(Cursol &c){
  int ret;
  string s;
  s += *c++;
  if(islower(*c)) s += *c++;
  ret = d[s];
  if(ret == 0) ok = false;
  return ret;
}
int Molecule(Cursol &c){
  if( *c == '\0' || *c == ')') return 0; 
  int ret = 0;
  if(*c == '('){
    ret = Molecule(++c) * Num(++c);
  }else if(isupper(*c)){
    ret = Atom(c);
    if(isdigit(*c)) ret *= Num(c);
  }
  return ret + Molecule(c);
}
int main(){
  string s;
  while(cin >> s , s != "END_OF_FIRST_PART"){
    cin >> d[s];
  }
  while(cin >> s , s != "0"){
    Cursol c = s.begin();
    ok = true;
    int ret = Molecule(c);
    if(ok) cout << ret << endl;
    else cout << "UNKNOWN" << endl;
  }
}

2013年12月14日土曜日

AOJ0570 ジグザグ数

解法

桁DPと呼ばれるらしい。
a以上b以下のジグザグ数の個数は、b以下のジグザグ数 - a-1以下のジグザグ数で求められる。
dp[自由に選べるか][直前が... 0:沿ってる 1:増 2:減][前の桁の数][余り][今の場所] という配列を用意。
自由に選べるかは、桁の数字に沿わなくなるまでfalseでやる。
余りは、(前の桁までの余り * 10 + 今の桁) % 倍数でやる。

ソース

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define MOD 10000
 
string s;
short dp[2][3][10][500][501]; //[free][増減][prev][余り][index]
int m;
 
int solve( bool fr, int updw, int pre, int mod, int index){
  if( index == s.size() ) return !mod;
  if( dp[fr][updw][pre][mod][index] != -1 ) return dp[fr][updw][pre][mod][index];
  int ret = 0, end = fr ? 9 : s[index];
  for(int i = 0, u ; i <= end ; i++ ){
    switch(updw){
    case 0: //さいしょ
      if( pre && pre == i ) continue;
      else if( pre == 0 ) u = 0;
      else if( pre > i ) u = 1;
      else u = 2;
      break;
    case 1: //次あっぷ
      if( pre >= i ) continue;
      else u = 2;
      break;
    case 2: //次だうん
      if( pre <= i ) continue;
      else u = 1;
      break;
    }
    ret += solve( fr|(i!=s[index]), u, i, (mod*10+i)%m, index+1);
  }
  return dp[fr][updw][pre][mod][index] = ret % MOD;
}
 
void java(){
  for(int i = s.size() - 1 ; i >= 0 ; i-- ){
    if(s[i] == 0) s[i] = 9;
    else{
      s[i]--;
      break;
    }
  }
}
 
int main(){
  string s1;
  cin >> s >> s1 >> m;
  for(int i = 0 ; i < s.size() ; i++ ) s[i] -= '0';
  java();
  fill_n( ****dp, 2 * 3 * 10 * 500 * 501, -1);
  int an = solve( 0, 0, 0, 0, 0);
  s = s1;
  for(int i = 0 ; i < s.size() ; i++ ) s[i] -= '0';
  fill_n( ****dp, 2 * 3 * 10 * 500 * 501, -1);
  int bn = solve( 0, 0, 0, 0, 0);
  cout << ( bn - an + MOD ) % MOD << endl;
}

2013年12月9日月曜日

AOJ0215 パチモンクリーチャー

解法

動的計画法じゃないかな?グラフにしたら勝った。
バグ死してた。

ソース

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
using namespace std;
#define INF ( 1 << 30 )
#define fr first
#define sc second
typedef pair < int , int > Pi;
typedef pair < Pi , Pi > Pii;
const int dy[] = { 0, 1, -1, 0}, dx[] = { 1, 0, 0, -1};
int h, w, Sx, Sy, Gx, Gy;
vector< Pi > hoge[5];
int dp[5][1000], num;
char c;
int dis(Pi x1,Pi x2){
  return abs(x1.fr - x2.fr) + abs(x1.sc - x2.sc);
}
int rec(int now,int java){
  if(dp[now][java]) return dp[now][java];
  int ret = INF, next = ( now + 1 ) % 5;
  if(now == num) ret = dis(hoge[now][java],Pi(Gy,Gx));
  else for(int i = 0 ; i < hoge[next].size() ; i++ ){
    ret = min( ret, rec( next, i) + dis(hoge[now][java],hoge[next][i]));
  }
  return dp[now][java] = ret;
}
int main(){
  while(cin >> w >> h , w){
    for(int i = 0 ; i < 5 ; i++ ) hoge[i].clear();
    for(int i = 0 ; i < h ; i++ ){
      for(int j = 0 ; j < w ; j++ ){
        cin >> c;
        if(c == 'S') Sy = i, Sx = j;
        else if(c == 'G') Gy = i, Gx = j;
        else if(c != '.') hoge[c-'1'].push_back(Pi(i,j));
      }
    }
    Pi ret = Pi(INF,INF);
    for(int i = 0 ; i < 5 ; i++ ){
        for(int j = 0 ; j < 5 ; j++ )
          for(int k = 0 ; k < 1000 ; k++ ) dp[j][k] = 0;
      num = ( i + 4 ) % 5;
      int next = ( i + 1 ) % 5, tmp = INF;
      for(int j = 0 ; j < hoge[next].size() ; j++ ){
        ret = min( ret, Pi(rec( next, j) + dis(Pi(Sy,Sx),hoge[next][j]), i+1));
      }
    }
    if( ret.fr == INF ) cout << "NA" << endl;
    else cout << ret.sc << " " << ret.fr << endl;
  }
}

2013年12月7日土曜日

string

暇だったのでまとめてみた。

AOJ2254 最短ルート

解法


bitでいろいろやるのは自明っぽかったので察して、
Dijkstraじゃね?って思ったがMLEで詰んでいた。
動的計画法らしかったのでそうした。
bit[クリアしてるかの状態] = 最短時間

ソース


#include<iostream>
#include<algorithm>
using namespace std;
#define INF ( 1 << 30 )
int N,mas[16][17],dp[1<<17];
 
int rec(int bit){
  if(dp[bit]) return dp[bit];
  if(!bit) return 0;
 
  int ret = INF;
  for(int i = 0 ; i < N ; i++ ){
    if(!(bit & (1 << i))) continue;
    int min_cost = mas[i][0];
    for(int j = 0 ; j < N ; j++ ){
      if(i != j && !(bit & (1 << j))) min_cost = min( min_cost, mas[i][j+1]);
    }
    ret = min( ret, min_cost + rec(bit&~(1 << i)));
  }
  return dp[bit] = ret;
}
 
int main(){
  while(cin >> N , N ){
    fill_n( dp, 1<<17, 0);
    for(int i = 0 ; i < N ; i++ ){
      for(int j = 0 ; j <= N ; j++ ){
        cin >> mas[i][j];
      }
    }
    cout << rec((1 << N)-1) << endl;
  }
}
ループ
#include<cstdio>
#include<algorithm>
using namespace std;
#define INF ( 1 << 25 )
int N,mas[16][17],dp[1<<16];
int solve(){
  for(int i = 0 ; i < 1 << N ; i++ ) dp[i] = INF;
  dp[(1 << N) - 1] = 0;
  for(int bit = (1 << N) - 1 ; bit >= 0 ; bit-- ){
    for(int i = 0 ; i < N ; i++ ){
      if(!(bit & (1 << i))) continue;
      int min_cost = mas[i][0];
      for(int j = 0 ; j < N ; j++ ){
        if(!(bit & (1 << j))) min_cost = min( min_cost, mas[i][j+1]);
      }
      dp[bit&~(1<<i)] = min(dp[bit&~(1<<i)],min_cost + dp[bit]);
    }
  }
  return dp[0];
}
int main(){
  while(scanf("%d",&N), N ){
    for(int i = 0 ; i < N ; i++ ){
      for(int j = 0 ; j <= N ; j++ ){
        scanf("%d",&mas[i][j]);
      }
    }
    printf("%d\n",solve());
  }
}


最初浮かんだ解法,Dijjkstraは状態数が多すぎた。
used[16][1<<16] = used[今の場所][bit]。今の場所が不要なことを察したかった。
上のDPと似てるけど修正したやつ。
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
#define fr first
#define sc second
#define INF ( 1 << 30 )
typedef pair < int , int > P;
int N, cost[16][17];
bool used[1<<16];
int Dijkstra(){
  priority_queue< P , vector<P> , greater<P> > que;
  for(int i = 0 ; i < N ; i++ ){
    que.push(P(cost[i][0],((1<<N)-1)&~(1<<i)));
  }
  fill_n( used, 1<<16, false);
  while(!que.empty()){
    P p = que.top();
    que.pop();
    if(p.sc == 0) return p.fr;
    if(used[p.sc]++) continue;
    for(int i = 0 ; i < N ; i++ ){
      if(!((p.sc >> i) & 1)) continue;
      int ret = cost[i][0];
      for(int j = 0 ; j < N ; j++ ){
        if(i != j && !((p.sc >> j) & 1)) ret = min(ret, cost[i][j+1]);
      }
      que.push(P(p.fr+ret,p.sc&~(1 << i)));
    }
  }
  return -1;
}
int main(){
  while(cin >> N , N){
    for(int i = 0 ; i < N ; i++ ){
      for(int j = 0 ; j <= N ; j++ ){
        cin >> cost[i][j];
      }
    }
    cout << Dijkstra() << endl;
  }
}

2013年12月1日日曜日

JOI2008 予選

タイムカード


秒に直して割ったり余りを求めたりして頑張る。
#include<iostream>
using namespace std;
int main(){
  int h1, m1, s1, h2, m2, s2;
  while(cin >> h1 >> m1 >> s1 >> h2 >> m2 >> s2){
    int tim = h2 * 3600 + m2 * 60 + s2 - h1 * 3600 - m1 * 60 - s1;
    cout << tim / 3600 << " " << tim % 3600 / 60 << " " << tim % 60 << endl;
  }
}

コンテスト


普通にやった。
#include<iostream>
#include<vector>
#include<algorithm>
#include<numeric>
using namespace std;
int main(){
  vector< int > A(10),B(10);
  for(int i = 0 ; i < 10 ; i++ ) cin >> A[i];
  for(int i = 0 ; i < 10 ; i++ ) cin >> B[i];
  sort(A.rbegin(),A.rend());
  sort(B.rbegin(),B.rend());
  cout << accumulate(A.begin(),A.begin()+3,0) << " "
       << accumulate(B.begin(),B.begin()+3,0) << endl;
}

連鎖


ちょっと頭使った。とりあえず全通り試す方針で、上へ下へと広げて連鎖をチェックする方針でいった。
#include<iostream>
#include<algorithm>
using namespace std;
#define INF ( 1 << 30 )
int n, d, ret, chain[10000];
int solve(int left){
  int right = left + 1, all = n;
  while(true){
    int color = chain[left], dis = 0;
    while( chain[left] == color && left >= 0 && ++dis ) left--;
    while( chain[right] == color && right < n && ++dis ) right++;
    if(dis < 4) return all;
    all -= dis;
  }
}
int main(){
  while(cin >> n, n){
    int ret = INF;
    for(int i = 0 ; i < n ; i++ ) cin >> chain[i];
    for(int i = 0 ; i < n ; i++ ){
      for(int j = 1 ; j < 4 ; j++ ){
        swap( chain[i], j);
        ret = min( ret, solve( i));
        swap( chain[i], j);
      }
    }
    cout << ret << endl;
  }
}

薄氷渡り


DFSでやったらすぐできた。
#include<iostream>
#include<algorithm>
using namespace std;
const int dy[] = { 1, 0, 0, -1}, dx[] = { 0, -1, 1, 0};
int h, w, mas[90][90];
bool used[90][90];
int dfs( int y, int x){
  if(used[y][x] || !mas[y][x]) return 0;
  used[y][x] = true;
  int ret = 0;
  for(int i = 0 ; i < 4 ; i++ ){
    int ny = y + dy[i], nx = x + dx[i];
    if(ny >= 0 && ny < h && nx >= 0 && nx < w){
      ret = max( ret, dfs( ny, nx) + 1);
    }
  }
  used[y][x] = false;
  return ret;
}
int main(){
  while(cin >> h >> w, h){
    for(int i = 0 ; i < h ; i++ ){
      for(int j = 0 ; j < w ; j++ ){
        cin >> mas[i][j];
      }
    }
    int ret = 0;
    for(int i = 0 ; i < h ; i++ ){
      for(int j = 0 ; j < w ; j++ ){
        ret = max( ret, dfs( i, j));
      }
    }
    cout << ret << endl;
  }
}

シャッフル


カードの枚数が10^9なので普通にやったら間に合わないのは自明。
シャッフルの回数が5000回以下なので,[始まりの番号,終わりの番号]を
pairで持たせて凄く頑張って実装すればできる気がする(震え声)。
どう考えてもバグって詰むのも自明だけど仕方ない。
#include<iostream>
#include<queue>
#include<map>
using namespace std;
#define fr first
#define sc second
typedef pair< int , int > P;
int n, m;
deque< P > d;
int _sum(P p){
  return p.sc - p.fr + 1;
}
int query(int& p,int& q,int& r){
  int ret = 0, ans = 0;
  while(ret + _sum(d.front()) < p){
    ret += _sum(d.front());
    d.pop_front();
  }
  if(ret != p - 1) d.front().fr += p - ret;
  ret = p;
  while(ret + _sum(d.front()) <= q){
    if(d.front().sc <= r) ans += _sum(d.front());
    else if(d.front().fr <= r) ans += _sum(P(d.front().fr,r));
    ret += _sum(d.front());
    d.pop_front();
  }
  if(!d.empty() && ret != q){
    P p = P(d.front().fr,d.front().fr + q - ret - 1);
    if(p.sc <= r) ans += _sum(P(p.fr,p.sc));
    else if(p.fr <= r) ans += _sum(P(p.fr,r));
  }
  return ans;
}
void shuffle(int* xy){
  deque< P > tmp[2];
  int ret = 0;
  for(int i = 0 ; i < 2 ; i++ ){
    while( ret + _sum(d.front()) <= xy[i]){
      tmp[i].push_back(d.front());
      ret += _sum(d.front());
      d.pop_front();
    }
    if(ret != xy[i]){
      tmp[i].push_back(P(d.front().fr,d.front().fr + xy[i] - ret - 1));
      d.front().fr = d.front().fr + xy[i] - ret;
      ret = xy[i];
    }
  }
  d.insert(d.end(),tmp[1].begin(),tmp[1].end());
  d.insert(d.end(),tmp[0].begin(),tmp[0].end()); 
}
int main(){
  while(cin >> n , n){
    cin >> m;
    d.push_back( P( 1, n));
    int p, q, r;
    cin >> p >> q >> r;
    while(m--){
      int xy[2];
      cin >> xy[0] >> xy[1];
      shuffle(xy);
    }
    cout << query(--p, q, r) << endl;
    d.clear();
  }
}

ビンゴ


上から下,左から右に向かって数字が大きくなっていることを利用する。
dp[今いる場所][今いる場所までの合計] = パターンの数
#include<iostream>
using namespace std;
int main(){
  int N, M, S, dp[51][3001];
  while(cin >> N >> M >> S , N){
    fill_n(*dp,51*3001,0);
    dp[0][0] = 1;
    for(int i = 1 ; i <= M ; i++ ){
      for(int j = N * N ; j ; j-- ){
        for(int k = i ; k <= S ; k++ ){
          dp[j][k] = (dp[j][k] + dp[j-1][k-i]) % 100000;
        }
      }
    }
    cout << dp[N*N][S] << endl;
  }
}