練習ログ (001)

練習で解いた問題を気が向いたらメモしてみることにした。

AOJ2155: Infected Computer

Source
Description
  • n台のコンピュータが互いにパケットを送る。ウィルスに感染しているコンピュータからパケットを受け取ったコンピュータはウィルスに感染する。パケットの送信ログが与えられるので、感染しているコンピュータの数を答えよ。
Solution
  • やるだけ。ソートと単純なシミュレーション。
Code
// テンプレートは省略

struct data{ int src,dst,time; };
bool cmp(const data& a, const data& b){ return a.time < b.time; }

int main(){
  int n,m;
  while(cin >> n >> m && n|m){
    vint v(n,0); v[0]=1;
    vector<data> u(m);
    REP(i,m) cin >> u[i].time >> u[i].src >> u[i].dst;
    sort(ALL(u),cmp);
    REP(i,m) v[u[i].dst-1]=v[u[i].dst-1]||v[u[i].src-1];
    cout << count(ALL(v),1) << endl;;
  }

}

AOJ2156: Magic Slayer

Source
Description
  • n体のモンスターのHPと、m種類の魔法が与えられる。それぞれの魔法はモンスター1体または全体に指定されたダメージを与え、指定された量のMPを消費する。敵を全滅させるための最小のMPはいくらか。
Solution
  • DP + simple search.
  • 単体魔法だけを使って全ての敵のHPを p 以下に減らし、全体魔法だけを使って p ダメージを与えればよい。
    • どの魔法をどれだけ使うかは、硬貨両替問題のようなDPで計算できる。
    • p は分からないので全探索する。
  • HPの上限 100,000 に比べて魔法の威力の上限 999,999 がずっと大きいのが引っ掛けポイント。
    • 100,000 ぐらいの配列を取ってDPすると、そのような魔法が配列の範囲チェックに弾かれて使われないということが起きたりするかも。
Code
// テンプレートは(ry

struct magic{ int m,d; };
typedef vector<magic> vmagic;

const int L=200000, inf=1<<20;

vint DP(vmagic &v){
  REP(i,v.size()) ls(v[i].d,100000);
  vint ans(L,inf);
  ans[0]=0;
  REP(i,v.size()) REP(j,L-v[i].d)
    ls(ans[j+v[i].d],ans[j]+v[i].m);
  for(int i=L-2;i--;) ls(ans[i],ans[i+1]);
  return ans;
}

int main(){
  int n,m;
  while(cin >> n && n){
    vint hp(n);
    REP(i,n) cin >> hp[i];

    vmagic single,all;
    cin >> m;
    REP(i,m){
      string t; int m,d;
      cin >> t >> m >> t >> d;
      if(t=="Single") single.push_back((magic){m,d});
      else            all.   push_back((magic){m,d});
    }

    vint ms=DP(single), ma=DP(all);

    int ans=inf;
    REP(i,L){
      int res=0;
      REP(j,n) res+=ms[max(0,hp[j]-i)];
      ls(ans,res+ma[i]);
    }

    cout << ans << endl;
  }
}

AOJ2157: Dial Lock

Source
Description
  • 10桁以下で同じ桁数の2つの数 A, B が与えられる。最小回数の操作でAをBにせよ。
    • 1回の操作で、連続する任意長の桁を +n (ただし、9の次は0) することができる。
Solution
  • まず、適切に数字を読みかえれば、Aは全ての桁が 0 とすることができる。
  • 1回の操作で少なくとも1つの数字を合わせることにしても問題ない。
  • 一番左の桁から1桁ずつ合わせていくことにして、同時に右何桁を一緒に回すかを全探索。O(n! * n).
  • 他の人に比べて遅いのは、枝狩りを全くしていないせいだろうか。
Code
// テンp(ry

const int L=10;

int rec(int v[],int n,int l){
  if(n==l) return 0;
  int d=v[l];
  int res=1<<20;
  FOR(i,l,n){
    v[i]=(v[i]-d+10)%10;
    ls(res,rec(v,n,l+1)+(d?1:0));
  }
  FOR(i,l,n) v[i]+=d, v[i]%=10;
  return res;
}

int main(){
  int n;
  while(cin >> n && n){
    string src,dst;
    cin >> src >> dst;
    int v[L];
    REP(i,n) v[i]=(src[i]-dst[i]+20)%10;

    cout << rec(v,n,0) << endl;
  }

}

テンプレート

  • ついでにテンプレートそのものも晒し上げ。
#include <iostream>
#include <sstream>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <numeric>
#include <algorithm>
#include <string>

#include <cassert>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <cstring>

#define REP(i,e) for(int i=0;i<(int)(e);i++)
#define FOR(i,b,e) for(int i=(int)(b);i<(int)(e);i++)
#define FORC(i,b,e,c) for(int i=(int)(b);i<(int)(e)&&(c);i++)
#define ALL(c) (c).begin(), (c).end()
#define EACH(it,c) for(__typeof((c).begin()) it=(c).begin();it!=(c).end();++it)

using namespace std;

typedef long long ll;
typedef vector<int> vint;
typedef vector<long long> vll;
typedef vector<string> vstring;
typedef vector<double> vdouble;

template<class T>void pp(T v,int n){ REP(i,n)cout<<v[i]<< ' ' ; cout << endl;}
template<class T>void pp(T v){ EACH(it,v) cout << *it << ' ' ; cout << endl; }
template<class T>T& ls(T& a,T b){ if(b<a) a=b; return a;}
template<class T>T& gs(T& a,T b){ if(b>a) a=b; return a;}
  • 簡単に説明してみる。
#include
  • ただのインクルード。
#define
  • 毎度おなじみバッドノウハウ
  • REPとFORはそれぞれrepとREPという名前にしてる人も多いみたい。自分の場合、マクロは大文字であって欲しいのと、ループは目立って欲しい (REPはシンタックスハイライトも効かないし *1 ) のでこうしてます。
  • FORCはフラグによって脱出できるループ。ぶっちゃけ全然使わない。goto使ったほうがずっと読みやすい。消すのが面倒なので残ってるだけ。
typedef
  • ll と vint は無いと結構面倒。
  • それ以外はあれば便利だけど、持ち込み禁止のコンテストでは特に書かない。
template関数
  • ppはpretty print. lylically先生のを参考にしました。上のが配列用、下のがコンテナ用。
    • 持ち込み禁止のコンテストでも、上のは書いといて損は無いと思う。配列用とは言っても vector にも使えるし。
  • ls, gs は今は亡き <?=, >?= 演算子 *2 *3 の代わり。あると便利だけど、わざわざ打ち込んでまでは使わない。そのおかげでコピペありのときでもうっかり使い忘れることがよくあります。

*1:.emacsを書けば済む話だけど

*2:GNU拡張構文

*3:追記: エスケープ参照にし忘れてたので修正