Round 1A (Google Code Jam 2009)

練習のためやってみた.
Bは問題文が長いので,まだ読んでいない.

(前回と同じテンプレート使用)

A

各baseに対するhappy数達を,無限リストとして,計算しておいて,最後にintersectionをとる.
という,作戦だが,どうも遅い.メモリを沢山使う.
やはり,これは配列つかって解く問題なのか?

main = do (n:_) <- getList
          forM_ [1..n] $ \i ->
              do bs <- getList
                 putStrLn.output i $ allHappy bs

expand :: (Integral a) => a -> a -> [a]
expand b = reverse.unfoldr f
    where f 0 = Nothing
          f x = let (q,r) = divMod x b
                in Just (r,q)

ssd :: (Integral a) => a -> a -> a
ssd b = sum.map (^2).expand b

isHappy :: (Integral t) => t -> t -> Bool
isHappy 2 _ = True
isHappy b n = hpart S.empty n
    where hpart s x | y == 1       = True
                    | S.member y s = False
                    | otherwise    = hpart (S.insert y s) y
                    where y = ssd b x

upp :: (Num a) => a -> a
upp b =  foldl' add 0 $ replicate 4 (b-1)
    where add x y = b * x + y

happy,happyMemo :: Integer -> [Integer]
happy 2 = [1..]
happy b = small ++ hpart (S.fromList small) [upp b+1..]
    where small = filter (isHappy b) [1..upp b]
          hpart s (x:xs) | S.member y s = x : hpart s xs
                         | otherwise    = hpart s xs
              where y = ssd b x
happyMemo = memo happy

allHappy :: [Integer] -> Integer
allHappy bs = head.tail.foldl1' intersect' $ map happyMemo bs

intersect' :: (Ord t) => [t] -> [t] -> [t]
intersect' (x:xs) (y:ys) =
    case x `compare` y of
      EQ -> x : intersect' xs ys
      LT -> intersect' xs (y:ys)
      GT -> intersect' (x:xs) ys


C++で配列ベースで実装してみたら,断然こっちのほうが効率良い.

#include <iostream>
#include <sstream>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

#define FOR(x,xs) for(__typeof((xs).begin()) x = (xs).begin(); x != (xs).end(); x++)
#define ALL(xs) ((xs).begin()), ((xs).end())

typedef vector<int> vi;

const int U = 11814486;

inline vi getvi()
{
  string s;
  getline(cin, s);
  istringstream is(s);
  vector<int> vi;
  for(int i; is >> i;)
    vi.push_back(i);
  return vi;
}

inline int getint()
{
  string s;
  getline(cin, s);
  istringstream is(s);
  int n;
  is >> n;
  return n;
}

inline int ssd (int n, int b) {
  int s = 0;
  while (n > 0) {
    s += (n % b) * (n % b);
    n /= b;
  }
  return s;
}

inline bool isHappy(int n, int b,bool (* h)[11],bool (* v)[11]){
  if (v[n][b]) return h[n][b];
  v[n][b] = true;
  return h[n][b] = isHappy(ssd(n,b),b,h,v);
}

inline int solve(bool (* h)[11],bool (* v)[11])
{
  vi bs = getvi();

  for (int x = 2;;)
    {
      FOR (b,bs) if (!isHappy(x,*b,h,v)) goto NEXT;
      return x;
    NEXT:
      x++;
    }
}

inline void init(bool (* h)[11],bool (* v)[11])
{
  for (int i = 0; i < U; i++)
    for (int j = 0; j < 11; j++)
      h[i][j] = v[i][j] = false;
  for (int j = 0; j < 11; j++) h[1][j] = v[1][j] = true;
}

int main() {
  bool (* h)[11] = new bool[U][11], (* v)[11] = new bool[U][11];
  init(h,v);

  int t = getint();

  for (int i = 1; i <= t; i++)
      cout << "Case #" << i << ": " << solve(h,v) << endl;
}

「空白区切りの整数の並びを1行読み込む」のやりかたが分からず,苦労した.
結局,どこかからのコピペ.

C

確率の問題であるが,DP(多くの確率の問題はDPな気がする).
そんなわけで,漸化式のようなものをたてて,あとはメモ化再帰

main = do
  (t:_) <- getList
  forM_ [1..t] $ \i -> do
         (c:n:_) <- getList
         putStrLn.output i $ expected c n

choose :: (Integral a) => a -> a -> Integer
choose n r = div (prod (n-r+1) n) $ prod 1 r
    where prod a b = product [toInteger a..toInteger b]

prob :: (Integral a, Fractional b) => a -> a -> a -> a -> b
prob c n g a = on (/) fromInteger num (choose c n)
    where num = choose (c-g) a * choose g (n-a)

expected :: Int -> Int -> Double
expected c n = e 0
    where eM = memo e
          e g | g == c    = 0
              | otherwise = (1 + sum [p a * eM (g + a) | a <-[1..min n (c-g)] ]) / (1 - p 0)
              where p a = prob c n g a

なんども書くが,MemoTrieが便利すぎる.