HackerRank Week of Code - 25 参加日記

25位/7510人の自己ベスト。Hardの1問を正答できたのと、Hard/Expertの部分点を取れたのが大きかった。以下、難易度Easyのものは省略する。


Baby Step, Giant Step

2次元上の座標(0,0)から(d,0)に移動する最小のステップ数を求めよ。ただし、1回のステップでは距離aまたは距離bのみ進むことができる。a,b,dの組み合わせはq個与えられるので、それぞれについて答えること。
1 <= q <= 10^5
1 <= a <= b <= 10^9
0 <= d <= 10^9

x座標軸上のみで考えると、(x1,0)から(x2,0)まで移動するステップ数は、
1、x2-x1がaまたはb:1
2、x2-x1がb*2より小さい:2
3、それ以外:(x1+b,0)から(x2,0)までのステップ数+1
となる。愚直に再帰で実装するとStackOverflowになるので、3の部分はループでbを足していく。

public static int GetAns(int a, int b, int d)
{
    var ret = 0;
    if (d > 2 * b)
    {
        ret = (d - 2 * b) / b;
        d -= ret * b;
    }
    while (d > b * 2)
    {
        d -= a;
        ret++;
    }

    if (d == a || d == b)
        ret += 1;
    else if (d != 0)
        ret += 2;

    return ret;
}


Stone Division

2人のプレーヤーが次のゲームを戦う。いずれも最善手を指したときに、勝利するのはどちらか。
・n個の石でできた山が1つある
・m個の一意なInt型の集合S = {  { s_0, s_1, ..., s_{m-1} } }がある
・プレーヤーは交互に手を指す。手番では、Sから任意の{ s_i }と任意の山1つを選んで、その集まりを {s_i}個の山に分割する。このとき、分割された集合は同数である必要がある
・指せる手がないプレーヤーが負けとなる
1 <= n <= 10^18
1 <= m <= 10
2 <=  {s_i} <= 10^18

NimやGrundy数の理解が前提。本番ではWL-Algorithmで解いて部分正答だった。
参考:grundy数を習得したかった - nanikakaのAOJ航海日誌
 { g(x) } を1つの山にx個の石があるときのGrundy数とする。
xをkで割ったとき、Grungy数は
 { g(d)\,xor\,g(d)\,xor\,...\,xor\,g(d)\,\, (k\,times) }
となる。よって、kが偶数のときは0、奇数のときはg(d)に一致する。(=kが偶数なら勝ち)

class Program
{
    static List<long> s;
    static Dictionary<long, bool> dict = new Dictionary<long, bool>();

    static void Main(string[] args)
    {
        var str = Console.ReadLine().Split();
        var n = long.Parse(str[0]);
        var m = Int32.Parse(str[1]);
        s = Console.ReadLine().Split().Select(st => long.Parse(st)).ToList();
        Console.WriteLine(GetAns(n) ? "First" : "Second");
    }

    static bool GetAns(long n)
    {
        var ret = false;
        if (dict.ContainsKey(n)) return dict[n];

        foreach (var k in s)
        {
            if (n % k != 0) continue;
            ret |= k % 2 == 0;
            if (ret) break;
            ret |= !GetAns(n / k);
            if (ret) break;
        }

        dict.Add(n, ret);
        return ret;
    }
}


Good Point

二次平面上に、n個の厳密な凸多角形と、m個の楕円(Ellipse)がある。すべての多角形と楕円が重なる点を求めよ。該当点は存在することが保障されている。また、該当するものであれば、どの点を答えても良い。
1 <= n <= 500
3 <=  {v_i } <= 1500 (多角形iの頂点数)
3 <=  { \sum_{i=0}^{n-1} v_i } <= 1500
1 <= m <= 1500
各座標は[ -10^4, 10^4 ]の範囲にある
楕円のSemi Major Axisは10^4以下の整数
誤差は10^-4まで許容

本番でこれが解けたのはうれしい。
凸多角形と楕円が重なる部分は、かならず凸型の図形になる。よって、最終的な凸型図形までの距離をf(x, y)とすると、xおよびyを固定してできる関数f(A, y), f(x, B)はいずれも凹型の曲線になる。なのでXとYそれぞれで三分探索すれば解が求められる。

class Program
{
    static void Main(string[] args)
    {
        //polygon
        var polygons = new List<Polygon>();
        var n = Int32.Parse(Console.ReadLine());
        for (int i = 0; i < n; i++)
        {
            var polygon = new Polygon();
            var v = Int32.Parse(Console.ReadLine());
            for (int j = 0; j < v; j++)
            {
                var str = Console.ReadLine().Split();
                var x = Int32.Parse(str[0]);
                var y = Int32.Parse(str[1]);
                polygon.AddPoint(new Pt(x, y));
            }
            polygons.Add(polygon);
        }

        //ellipses
        var ellipses = new List<Ellipse>();
        var m = Int32.Parse(Console.ReadLine());
        for (int i = 0; i < m; i++)
        {
            var str = Console.ReadLine().Split();
            var x1 = Int32.Parse(str[0]);
            var y1 = Int32.Parse(str[1]);
            var x2 = Int32.Parse(str[2]);
            var y2 = Int32.Parse(str[3]);
            var a = Int32.Parse(str[4]);
            ellipses.Add(new Ellipse(x1, y1, x2, y2, a));
        }

        var lx = -10001.0;
        var rx = 10001.0;
        var ly = -10001.0;
        var ry = 10001.0;

        // ternary search in x
        for (int i = 0; i < 48; i++)
        {
            var x1 = lx + (rx - lx) / 3;
            var x2 = rx - (rx - lx) / 3;

            var ly_x1 = -10001.0;
            var ry_x1 = 10001.0;
            var x1Score = GetScore_TernaryY(ref ly_x1, ref ry_x1, x1, ellipses, polygons);

            var ly_x2 = -10001.0;
            var ry_x2 = 10001.0;
            var x2Score = GetScore_TernaryY(ref ly_x2, ref ry_x2, x2, ellipses, polygons);

            if (x2Score > x1Score)
            {
                //remove right
                rx = x2;
            }
            else
            {
                //remove left
                lx = x1;
                ly = ly_x2;
                ry = ry_x2;
            }
        }

        Console.WriteLine(lx);
        Console.WriteLine(ly);
    }

    static double GetScore_TernaryY(ref double ly, ref double ry, double x, List<Ellipse> ellipses, List<Polygon> polygons)
    {
        var score = GetScore(x, ly, ellipses, polygons);
        for (int i = 0; i < 48; i++)
        {
            var y1 = ly + (ry - ly) / 3;
            var y2 = ry - (ry - ly) / 3;

            var y1Score = GetScore(x, y1, ellipses, polygons);
            var y2Score = GetScore(x, y2, ellipses, polygons);

            if (y2Score > y1Score)
            {
                //remove right
                ry = y2;
            }
            else
            {
                //remove left
                ly = y1;
                score = y2Score;
            }
        }
        return score;
    }

    static double GetScore(double x, double y, List<Ellipse> ellipses, List<Polygon> polygons)
    {
        return GetScore(new Pt(x, y), ellipses, polygons);
    }

    static double GetScore(Pt pt, List<Ellipse> ellipses, List<Polygon> polygons)
    {
        return ellipses.Sum(e => e.GetDist(pt)) + polygons.Sum(p => p.GetDist(pt));
    }
}


public class Polygon
{
    public List<Edge> Edges;

    List<Pt> _points;

    public Polygon()
    {
        Edges = new List<Edge>();
        _points = new List<Pt>();
    }

    public void AddPoint(Pt point)
    {
        _points.Add(point);
        if (_points.Count >= 2)
        {
            Edges.Add(new Edge(_points[_points.Count() - 2], _points[_points.Count() - 1]));
        }
    }

    public double GetDist(Pt pt)
    {
        double minDist = Double.MaxValue;
        var inside = true;

        for (int i = 0; i < Edges.Count(); i++)
        {
            if (!IsCcw(pt, Edges[i].p1, Edges[i].p2))
            {
                inside = false;
            }

            minDist = Math.Min(minDist, Edges[i].Dist(pt));
        }

        return inside ? 0 : minDist;
    }

    public bool IsCcw(Pt p, Pt p1, Pt p2)
    {
        return Pt.Ccw(p, p1, p2) == 1;
    }
}

public class Ellipse
{
    public Pt P1;
    public Pt P2;
    public double A;
    public double D;

    public Ellipse(double x1, double y1, double x2, double y2, double a)
    {
        P1 = new Pt(x1, y1);
        P2 = new Pt(x2, y2);
        A = a;
        D = 2 * a;
    }

    public double GetDist(Pt pt)
    {
        var ret = P1.Dist(pt) + P2.Dist(pt);
        return ret <= D ? 0 : (ret - D) / 2;
    }
}

PtとEdgeは個人ライブラリのもの。


DAG Queries

頂点数n、辺数mのDAGが与えられる。すべての頂点vは値として整数a[v]持ち、これの初期値は0である。次のクエリの結果を答えよ。クエリは3タイプある。
1. 1 u x: 頂点uから訪れることのできる頂点vについて、a[v]をxに更新する
2. 2 u x: 頂点uから訪れることのできる頂点vについて、a[v]>xならa[v]をxに更新する
3. u: a[u]を出力する
2 <= n <= 10^5
1 <= m, q <= 10^5 (qはクエリの数)
0 <= x <= 10^9

すべてのクエリをため込んで出力のたびにa[v]を算出する方法も、クエリが来るたびに各a[i]を算出する方法もTLEになる。平方分割を使って解決する。

  1. すべてのクエリをローカル変数に保持
  2. クエリタイプ2について、{ \sqrt{q} }くらいのブロックごとに、ブロックkの時点でクエリ2だけでa[v]の値がどうなるかの情報、type_2[k][v]を作成する
  3. クエリを最初から処理:(1)クエリ1はキューque1に入れていく。que1のサイズが{ \sqrt{q} }を超えたらその時点でクエリ1だけで値がどうなるかの情報type_1[v]を作成する(2)クエリ3が来たらtype_2とtype_1を使って出力値を計算

Editorialによると、グラフすべてを計算しようとするとメモリが足りなくなるので、グラフを3つくらいに分割して3回処理を回す必要がある。
理屈はわかるのだが、C#で実装したらTLEになってしまった・・・。

HackerRank Week of Code - 24 参加日記

392位/9177人。難易度Hardの「XOR Matrix」が解けそうで解けなく3日ほど苦しんだ(結果は部分正解)。Easy問題は省略する。


Simplified Chess Engine

盤の大きさが4x4、かつクイーン・ルーク・ナイト・ビショップだけが存在するミニチェスを考える。勝利条件は相手のクイーンを取ることとする。盤面g個が与えられたとき、m手以内に先手が必勝かどうかをそれぞれ答えよ。
先手後手それぞれ、クイーンが1個、ルークが0~2個、ナイトとビショップが合計0~2個ある
1 <= g <= 200
1 <= m <= 6

十分に小さいゲーム木なので、MinMax法で簡単に求められる。ただし実装が少し重い。ちなみに、所定の手数までの勝敗なので、Zobrist Hashなどで同一局面を排除するとWAになる。

class Program
{
    enum PIECES { EMPTY, Q, N, B, R, q, n, b, r };

    static void Main(string[] args)
    {
        var g = Int32.Parse(Console.ReadLine());

        while( g > 0 )
        {
            var str = Console.ReadLine().Split();
            var w = Int32.Parse(str[0]);
            var b = Int32.Parse(str[1]);
            var m = Int32.Parse(str[2]);
            var board = GetInitBoard();

            while ( w > 0 ) 
            {
                SetPiece_Init(true, board, Console.ReadLine().Split());
                w--;
            }

            while ( b > 0 ) 
            {
                SetPiece_Init(false, board, Console.ReadLine().Split());
                b--;
            }

            if (MinMax('W', board, m)) Console.WriteLine("YES");
            else Console.WriteLine("NO");

            g--;
        }
    }

    static PIECES[,] GetInitBoard()
    {
        var ret = new PIECES[4, 4];
        for (int row = 0; row < 4; row++)
        {
            for (int col = 0; col < 4; col++)
            {
                ret[row, col] = PIECES.EMPTY;
            }
        }
        return ret;
    }

    static void SetPiece_Init(bool isWhite, PIECES[,] board, string[] inStrs)
    {
        var piece = GetPiece(!isWhite ? inStrs[0].ToLower()[0] : inStrs[0][0]);
        var col = inStrs[1][0] - 'A';
        var row = inStrs[2][0] - '1';
        MovePiece(board, -1, -1, row, col, piece).Item2;
    }

    static PIECES GetPiece(char p)
    {
        switch (p) {
            case 'Q':
                return PIECES.Q;
            case 'N':
                return PIECES.N;
            case 'B':
                return PIECES.B;
            case 'R':
                return PIECES.R;
            case 'q':
                return PIECES.q;
            case 'n':
                return PIECES.n;
            case 'b':
                return PIECES.b;
            case 'r':
                return PIECES.r;
        }
        return PIECES.EMPTY;
    }

    //return IsTerminalState
    static bool MovePiece(PIECES[,] board, int prevRow, int prevCol, int row, int col, PIECES piece)
    {
        var isTerminal = false;

        if (prevRow != -1) board[prevRow, prevCol] = PIECES.EMPTY;

        if (board[row, col] != PIECES.EMPTY)
        {
            if (board[row, col] == PIECES.Q || board[row, col] == PIECES.q) isTerminal = true;
        }

        board[row, col] = piece;
        return isTerminal;
    }

    static char GetNextTurn(char turn)
    {
        return turn == 'W' ? 'B' : 'W';
    }

    static bool IsHisPiece(char turn, PIECES piece)
    {
        if(piece == PIECES.EMPTY) return false;

        if (turn == 'W')
            return piece == PIECES.Q || piece == PIECES.N || piece == PIECES.B || piece == PIECES.R;

        if (turn == 'B')
            return piece == PIECES.q || piece == PIECES.n || piece == PIECES.b || piece == PIECES.r;

        return false;
    }

    static bool Win(PIECES[,] board, char turn)
    {
        bool Q_exist = false;
        bool q_exist = false;

        for (int row = 0; row < 4; row++)
        {
            for (int col = 0; col < 4; col++)
            {
                if(board[row, col] == PIECES.Q) Q_exist = true;
                if(board[row, col] == PIECES.q) q_exist = true;
            }
        }

        if(turn == 'W' && !q_exist) return true;
        if(turn == 'B' && !Q_exist) return true;
        return false;
    }

    static PIECES[,] CopyBoard(PIECES[,] org)
    {
        var ret = new PIECES[4, 4];
        for (int row = 0; row < 4; row++)
        {
            for (int col = 0; col < 4; col++)
            {
                ret[row, col] = org[row, col];
            }
        }
        return ret;
    }

    static bool IsInRange(int row, int col)
    {
        return IsInRange(row) && IsInRange(col);
    }

    static bool IsInRange(int pos)
    {
        return pos >= 0 && pos < 4;
    }

    static int[,] Moves_N = new int[,]
    {
        { 2, 1}, { 1, 2}, { -1, 2}, { -2, 1}, { -2, -1}, { -1, -2}, { 1, -2}, { 2, -1},
    };

    static bool IsEnemy(PIECES p, PIECES p2)
    {
        if (p == PIECES.Q || p == PIECES.R || p == PIECES.B || p == PIECES.N)
            return p2 == PIECES.q || p2 == PIECES.r || p2 == PIECES.b || p2 == PIECES.n;

        if (p == PIECES.q || p == PIECES.r || p == PIECES.b || p == PIECES.n)
            return p2 == PIECES.Q || p2 == PIECES.R || p2 == PIECES.B || p2 == PIECES.N;

        return false;
    }

    static void TryMove(ref List<Tuple<int, int>> moves, PIECES[,] board, int row, int col, PIECES piece, int rowDiff, int colDiff)
    {
        row += rowDiff;
        col += colDiff;

        while (IsInRange(row, col))
        {
            if (board[row, col] == PIECES.EMPTY)
            {
                moves.Add(new Tuple<int, int>(row, col));
            }
            else if(IsEnemy(piece, board[row, col]))
            {
                moves.Add(new Tuple<int, int>(row, col));
                break;
            }
            else
            {
                break;
            }
            row += rowDiff;
            col += colDiff;
        }
    }

    static List<Tuple<int, int>> GetNextPositions(int row, int col, PIECES piece, PIECES[,] board)
    {
        var ret = new List<Tuple<int, int>>();

        if (piece == PIECES.N || piece == PIECES.n)
        {
            for (int i = 0; i < Moves_N.GetLength(0); i++)
            {
                var nextRow = row + Moves_N[i, 0];
                var nextCol = col + Moves_N[i, 1];
                if (IsInRange(nextRow, nextCol))
                {
                    if(board[nextRow, nextCol] == PIECES.EMPTY || IsEnemy(piece, board[nextRow, nextCol]))
                        ret.Add(new Tuple<int, int>(nextRow, nextCol));
                }
            }
        }
        else
        {
            switch (piece)
            {
                case PIECES.Q:
                case PIECES.q:
                    TryMove(ref ret, board, row, col, piece, 1, 0);
                    TryMove(ref ret, board, row, col, piece, 0, 1);
                    TryMove(ref ret, board, row, col, piece, -1, 0);
                    TryMove(ref ret, board, row, col, piece, 0, -1);
                    TryMove(ref ret, board, row, col, piece, 1, 1);
                    TryMove(ref ret, board, row, col, piece, -1, 1);
                    TryMove(ref ret, board, row, col, piece, 1, -1);
                    TryMove(ref ret, board, row, col, piece, -1, -1);
                    break;
                case PIECES.R:
                case PIECES.r:
                    TryMove(ref ret, board, row, col, piece, 1, 0);
                    TryMove(ref ret, board, row, col, piece, 0, 1);
                    TryMove(ref ret, board, row, col, piece, -1, 0);
                    TryMove(ref ret, board, row, col, piece, 0, -1);
                    break;
                case PIECES.B:
                case PIECES.b:
                    TryMove(ref ret, board, row, col, piece, 1, 1);
                    TryMove(ref ret, board, row, col, piece, -1, 1);
                    TryMove(ref ret, board, row, col, piece, 1, -1);
                    TryMove(ref ret, board, row, col, piece, -1, -1);
                    break;
            }
        }

        return ret;
    }

    //turn: 'W' or 'B'
    //return true if the player won
    static bool MinMax(char turn, PIECES[,] board, int depth)
    {
        //White could not capture black's Qween within m moves
        if (depth == 0) return turn == 'W' ? false : true;

        var nextTurn = GetNextTurn(turn);

        for (int row = 0; row < 4; row++)
        {
            for (int col = 0; col < 4; col++)
            {
                if (IsHisPiece(turn, board[row, col]))
                {
                    foreach (var nextPos in GetNextPositions(row, col, board[row, col], board))
                    {
                        var nextBoard = CopyBoard(board);
                        var nextIsTerm = MovePiece(nextBoard, row, col, nextPos.Item1, nextPos.Item2, board[row, col]);

                        if (nextIsTerm)
                        {
                            if (Win(nextBoard, turn)) return true;
                        }
                        else
                        {
                            if (!MinMax(nextTurn, nextBoard, depth - 1)) return true;
                        }
                    }
                }
            }
        }
        return false;
    }
}


XOR Matrix

数列a[0], a[1]... a[n]が与えられる。次の操作をm-1回行ったときの最終的な値を出力せよ。

  • i < n-1 ならa[i]へ XOR(a[i], a[i+1])を代入
  • i = n-1 ならa[i]へ XOR(a[i], a[0])を代入

1 <= n <= 10^5
0 <= m <= 10^18 - 1
0 <= a[j] <= 10^9

たとえば要素0にXOR加算する要素は、
1
1 1
1 0 1
1 1 1 1
1 0 0 0 1
1 1 0 0 1 1
1 0 1 0 1 0 1
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 0 1
のようにパスカルのフラクタルと同じ形になる。
(最上段は初期状態、その後は上から、操作1終了後、操作2終了後・・・の順)
パスカルのフラクタルがすべて1になるのは2^k回目のときで、その次の状態が該当要素と最終要素のXORになる。よって、現在の状態から2^kだけ飛ばして計算することが可能。
要素0以外も、シフトするだけで同じパターンとなる。

static void Main(string[] args)
{
    var str = Console.ReadLine().Split();
    var n = Int32.Parse(str[0]);
    var m = long.Parse(str[1]);
    var a = ReadLine().Split().Select(s => long.Parse(s)).ToArray();

    m--;
    while (m > 0)
    {
        long cnt = 1;
        while (cnt << 1 <= m)
        {
            cnt <<= 1;
        }

        var tmp = new long[a.Length];
        for (long i = 0; i < a.Length; i++)
        {
            tmp[i] = a[i] ^ a[(i + cnt) % a.Length];
        }
        a = tmp;
        m -= cnt;
    }

    foreach (var v in a)
    {
        Console.Write(v.ToString() + " ");
    }
    Console.WriteLine();
}


Shashank and the Palindromic Strings

文字列のリスト { a = [a_0, a_1, ..., a_{n-1} ] }がq個与えられる。それぞれについて次の条件を満たすパターン数を答えよ。

  •  { s_{i} } {a_{i}} の空文字以外のサブシーケンスとしたとき、 {s_0 + s_1+ ...+ s_{n-1}} が回文になる

1 <= q <= 50
1 <= n <= 50

全文字列を連結した文字列sを考える。DPを次のように定義する。
 { dp[r, c, was1, was2] } :セグメント[r, c]で回文になるサブシーケンスのパターン数。
 was1: s[r]の元文字列にある文字を含むかどうか
 was2: s[c]の元文字列にある文字を含むかどうか
更新条件は2通りある。
1. s[r]とs[c]の元文字列が同じとき

// r・cの元文字列を使わないのは空文字のパターンのみ
dp[r, c, 0, 0] = 1;

// s[r]とs[l]を回文として使わないパターン
dp[r, c, 1, 1] = dp[r, c - 1, 1, 1] + dp[r + 1, c, 1, 1];
dp[r, c, 1, 1] -= dp[r + 1, c - 1, 1, 1]; // 重複を排除

// s[r]とs[l]を回文として使うパターン
if (s[r] == s[c])
{
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1];
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 0];
}

2. s[r]とs[c]の元文字列が違うとき
2-1. s[r+1]とs[r]の元文字列が違い、さらにs[c-1]とs[c]の元文字列が違う場合

// rが元文字列の右端で、cが元文字列の左端のとき

// どちらの文字列も使わない場合は、その内側の結果と同じ
dp[r, c, 0, 0] = (r + 1 == c ? 1 : dp[r + 1, c - 1, 1, 1]);

// どちらかの文字列を使う場合は、使わない側をはぶいたパターン数に一致
dp[r, c, 1, 0] = dp[r, c - 1, 1, 1];
dp[r, c, 0, 1] = dp[r + 1, c, 1, 1];

// どちらも使うとき。s[r]とs[c]が一致しないと0になる
dp[r, c, 1, 1] = (s[r] == s[c] ? dp[r, c, 0, 0] : 0); 

2-2. s[r+1]とs[r]の元文字列が違うが、s[c-1]とs[c]の元文字列は同じ場合

// s[r]が元文字列の右端だがs[c]は元文字列の左端ではないとき

dp[r, c, 0, 0] = dp[r, c - 1, 0, 0];    //s[r]、s[c]いずれの元文字列も使わない
                                        //dp[r + 1, c - 1, 0, 0]のような気がするが・・・?
dp[r, c, 1, 0] = dp[r, c - 1, 1, 0];    //s[r]の元文字列を使う
dp[r, c, 0, 1] = dp[r + 1, c, 1, 1];    //s[r]の元文字列を使わない
dp[r, c, 1, 1] = dp[r, c - 1, 1, 1];    //どっちも使う->s[r]を使うしかない

if (s[r] == s[c])
{
    //s[r]とs[c]が同じなら、この2つを回文に使うパターンが追加される
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1];
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 0];

    //元文字列が隣り合っていた場合!
    if (A[r] + 1 == A[c])
    {
        dp[r, c, 1, 1]++; // 1増えるのみ
    }
}

2-3. s[r+1]とs[r]の元文字列が同じだが、s[c-1]とs[c]の元文字列は違う場合
 (2-2と同様)
2-4. s[r+1]とs[r]の元文字列も、s[c-1]とs[c]の元文字列も違う場合

// s[r]が元文字列の右端ではなく、s[c]が元文字列の左端ではないとき

// どちらの元文字列も使わないときは1つ内側の結果と同じ
dp[r, c, 0, 0] = dp[r + 1, c - 1, 0, 0];

// 片方の元文字列を使わないときは、使わない側を1つ内側にした結果と同じ
dp[r, c, 0, 1] = dp[r + 1, c, 0, 1];
dp[r, c, 1, 0] = dp[r, c - 1, 1, 0];

// どちらの元文字列も使うとき
dp[r, c, 1, 1] = dp[r + 1, c, 1, 1] + dp[r, c - 1, 1, 1];
dp[r, c, 1, 1] -= dp[r + 1, c - 1, 1, 1];   // 重複を排除

if (s[r] == s[c])
{
    // s[r]とs[c]が同じ文字なら、両方の元文字列を使うパターンに
    // 内側の結果すべてが加算される
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1];
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 0];
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 1];
    dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 0];
}

最終的な実装は省略。

ARC049-ARC058+ABC043メモ(練習)

AtCoder Beginner Contest 043 - AtCoder Beginner Contest 043 | AtCoder
D - アンバランス / Unbalanced

文字列tについて、tの長さが2以上であり、かつ過半数の文字が同じとき、tをアンバランスを呼ぶ。文字列sからアンバランスな部分文字列を探して、その位置を出力せよ。複数ある場合は、どれを出力してもよい。
2 <= |s| <= 10^5
sは小文字のアルファベットのみ

過半数かどうかなので、連続する3文字分のみ判定すればよい。

if (s.Length == 2)
{
    if (s[0] == s[1]) Console.WriteLine("1 2");
    else Console.WriteLine("-1 -1");
    return;
}

for (int i = 0; i < s.Length - 2; i++)
{
    for (int c = 'a'; c <= 'z'; c++)
    {
        var cnt = 0;
        for (int j = 0; j < 3; j++) if (s[i + j] == c) cnt++;
        if (cnt >= 2)
        {
            Console.WriteLine(string.Format("{0} {1}", i + 1, i + 3));
            return;
        }
    }
}
Console.WriteLine("-1 -1");


AtCoder Regular Contest 058 - AtCoder Regular Contest 058 | AtCoder
C: こだわり者いろはちゃん / Iroha's Obsession - AtCoder Regular Contest 058 | AtCoder

嫌いな数字D1,D2...Dkある。10進表記でこれらの数字を使わずN円のものを買うとき、支払額の最小はいくらか。

Nから1ずつ増やして試していけばよい。

for (int i = n; i < Int32.MaxValue; i++)
{
    if (!i.ToString().Select(c => Int32.Parse(c.ToString())).ToList().Any(c => cannot_use.Contains(c)))
    {
        Console.WriteLine(i);
        break;
    }
}


D: いろはちゃんとマス目 / Iroha and a Grid - AtCoder Regular Contest 058 | AtCoder

縦Hマス、横Wマスのマス目がある。1番左上のマスから、右が左に1マスずつ進める。このとき、一番右下に行く方法は何通りあるか。
ただし、下からAマス以内かつ左からBマス以内は通れない。
1 <= H, W <= 100000
1 <= A < H
1 <= B < W

f:id:yambe2002:20160726121234p:plain
左上から図の①までの経路数を求め、それぞれに②右下までの経路数を掛けて合計すればよい。

const Int64 mod = 1000000007;
static Int64 Solve(int h, int w, int a, int b)
{
    Precal_FactAndInv(mod);

    Int64 ret = 0;
    for (int i = 0; i < h - a; i++)
    {
        var c = Nck(b + i - 1, i, mod);
        c *= Nck(w - b - 1 + h - 1 - i, h - 1 - i, mod);
        ret += c;
        ret %= mod;
    }
    return ret;
}

本番でNck()をあらかじめ用意していなかったので、TLEにならない解をうまく作れなかった。


E: 和風いろはちゃん / Iroha and Haiku - AtCoder Regular Contest 058 | AtCoder

長さNの、それぞれの値が1~10の数列は全部で10^N通りある。このうち、XYZを含むものは何通りか。XYZを含むとは以下のように定義する。
{ a_x + a_{x+1} + ... + a_{y-1} = X }
{ a_y + a_{y+1} + ... + a_{z-1} = Y }
{ a_z + a_{z+1} + ... + a_{w-1} = Z }
を満たす{ 0 \leq x < y < z < w \leq z }が存在する。
3 <= N <= 40
1 <= X <= 5
1 <= Y <= 7
1 <= Z <= 5

全パターン(10^n)からXYZを含まないパターンを引いて求める。含まないパターンは次のDPを定義して計算。
・DP[i][s]:0~iまでで、S={2つ前の値, 1つ前の値, 今回の値}の場合の組み合わせ合計
Sの各値を単純に1~10で持つと状態数が多すぎて計算できないが、X, Y, Zの合計が17しかないことに着目し、1をb1、2をb10・・・というように二進数で管理すれば、Sは17ビットまで小さくなる。

Int64 ret = 1;
for (int i = 0; i < n; i++)
{
    ret *= 10;
    ret %= mod;
}

var possibleMask = 1 << (x + y + z);
possibleMask |= 1 << (y + z);
possibleMask |= 1 << (z);

var mask = (1 << (x + y + z + 1)) - 1;
var prevDp = new UInt64[mask + 1];
var curDp = new UInt64[mask + 1];

prevDp[1] = 1;  //ループ1回目は今回の値だけ存在する!
for (int cnt = 0; cnt < n; cnt++)
{
    for (int prevState = 0; prevState <= mask; prevState++)
    {
        for (int i = 1; i <= 10; i++)
        {
            var nextState = ((prevState << i) | (1)) & mask;

            if ((possibleMask & nextState) == possibleMask) continue;
            curDp[nextState] += prevDp[prevState];
            curDp[nextState] %= mod;
        }
    }

    prevDp = curDp;
    curDp = new UInt64[mask + 1];
}

for (int state = 0; state < mask + 1; state++)
{
    ret -= (Int64)prevDp[state];
    ret %= mod;
}

ret = (ret + mod) % mod;    //負になる場合あり
Console.WriteLine(ret);

試しにXYZを含める場合で計算しようとしたが断念。


ARC052

A: 何期生? - AtCoder Regular Contest 052 | AtCoder

文字列Sには、1つの連続する数字が含まれる。この数字を出力せよ。

そのまま。正規表現で解いてみた。

Console.WriteLine(new Regex(@"[^0-9]").Replace(Console.ReadLine(), ""));


B: 円錐 - AtCoder Regular Contest 052 | AtCoder

三次元空間にN個の円錐が、重ならないように浮いている。どの円錐も、底辺がyz平面と水平である。円錐[i]は、底辺のx座標x[i]、半径r[i]、高さh[i]が与えられる。
このとき、Q個の以下のクエリに答えよ
 二つの整数AとBが与えられたとき、A<=x<=Bとなる空間のうち、いずれかの円錐の内側にある部分の体積を求める
1<=N<=100、1<=Q<=100000、0<=x[i]<=10000、1<=h[i]<=10000、1<=R[i]<=1000
0<=A[i]<=B[i]<=20000

v[p](pは0~20000)をx座標[0,p]の空間のうち円錐の内側にある部分の体積合計、と定義して前もって計算した。各クエリに対しv[B]-v[A]が答えとなる。
もっとも、工夫無しで普通に解いても時間内に間に合うようだ。

var v = new double[20001];
for (int p = 1; p < 20001; p++)
{
    for (int i = 0; i < n; i++)
    {
        if (p <= x[i]) continue;
        var v1 = (1.0 / 3.0) * Math.PI * Math.Pow((double)r[i], 2) * (double)h[i];
        var v2 = p >= x[i] + h[i] ? 0 : (1.0 / 3.0) * Math.PI * Math.Pow((double)r[i] * ((double)(h[i] - (p - x[i])) / (double)h[i]), 2) * (double)(h[i] - (p - x[i]));
        v[p] += (v1 - v2);
    }
}

//result
for (int i = 0; i < q; i++)
{
    Console.WriteLine(v[b[i]] - v[a[i]]);
}


C: 高橋くんと不思議な道 - AtCoder Regular Contest 052 | AtCoder

町0~N-1がある。町の間にはいくつかの道があり、道はタイプAとタイプBに分けられる。タイプAを通るとコスト1、タイプBを通ると(今までに通ったタイプBの数)+1のコストがかかる。町0からそれぞれの町までの最小コストを求めよ。
1 <= N <= 10^4
1 <= M <= 10^5

現在の町Idx・コスト・今までのタイプBの合計、を保持してDijkstraをするだけだが、適切に枝刈りをしないとTLEになる。

static void Main(string[] args)
{
    ///Std入力は省略

    //Dijkstra
    var que = new PriorityQueue<Path>();
    var min_bnum = new int[n];
    var min_cost = new int[n];
    for (int i = 0; i < n; i++)
    {
        min_bnum[i] = Int32.MaxValue;
        min_cost[i] = Int32.MaxValue;
    }
    que.Push(new Path(0, 0, 0));

    while (que.Count() > 0)
    {
        var path = que.Pop();
        if (NoNeed(path, min_bnum, min_cost)) continue;
        Update(path, min_bnum, min_cost);

        foreach (var typeA in e_typeA[path.Idx])
        {
            var newPath = new Path(typeA, path.Cost + 1, path.Bnum);
            if (NoNeed(newPath, min_bnum, min_cost)) continue;    //Queueに入れる前にカットする
            que.Push(newPath);
        }

        foreach (var typeB in e_typeB[path.Idx])
        {
            var newPath = new Path(typeB, path.Cost + path.Bnum + 1, path.Bnum + 1);
            if (NoNeed(newPath, min_bnum, min_cost)) continue;    //Queueに入れる前にカットする
            que.Push(newPath);
        }
    }

    for (int i = 0; i < n; i++)
        Console.WriteLine(min_cost[i]);
}

static bool NoNeed(Path path, int[] min_bnum, int[] min_cost)
{
    if (path.Bnum >= min_bnum[path.Idx] && path.Cost >= min_cost[path.Idx]) return true;
    return false;
}

static void Update(Path path, int[] min_bnum, int[] min_cost)
{
    min_bnum[path.Idx] = Math.Min(min_bnum[path.Idx], path.Bnum);
    min_cost[path.Idx] = Math.Min(min_cost[path.Idx], path.Cost);
}


ARC051
A: 塗り絵 - AtCoder Regular Contest 051 | AtCoder

二次平面がある。(x1, y1)から距離r以下の部分を青く塗り、つぎに(x2, y2)から(x3, y3)までの長方形内を赤く塗る。両方の色が塗られた箇所は紫になる。赤、青の有無を答えよ。

青の有無は、4頂点が赤の中心からr以上離れているかどうかで判定できる。

Console.WriteLine(x1 - r < x2 || x1 + r > x3 || y1 + r > y3 || y1 - r < y2 ? "YES" : "NO");
Console.WriteLine(GetSqDist(x1, y1, x2, y2) > r * r || GetSqDist(x1, y1, x2, y3) > r * r || GetSqDist(x1, y1, x3, y2) > r * r || GetSqDist(x1, y1, x3, y3) > r * r ? "YES" : "NO");

static double GetSqDist(double x1, double y1, double x2, double y2)
{
    return Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2);
}


B: 互除法 - AtCoder Regular Contest 051 | AtCoder

ユークリッドの互除法でGCDを求める以下のコードがある。

int counter = 0;
int gcd(int a, int b) {
    if (b == 0) return a;
    counter++;
    return gcd(b, a%b);
}

最終的なcounterの値がKとなる、aとbの組み合わせを答えよ。

フィボナッチ数列のn(k)とn(k-1)が解答になる。

ulong prev = 1;
ulong cur = 1;
for (int i = 0; i < k; i++)
{
    var wk = cur;
    cur = prev + cur;
    prev = wk;
}

Console.WriteLine(string.Format("{0} {1}", cur, prev));


C: 掛け算 - AtCoder Regular Contest 051 | AtCoder

整数a1, a2, ...aNが与えられたとき、一番小さいものをA倍する操作をB回繰り返した結果を、昇順に並び変えて出力せよ。ただし結果は10^9+7でModする。
1 <= N <= 50
1 <= ai <= 10^9
1 <= A, B <= 10^9

最小値にどんどんAを掛けていくと、ある時点から、最小値*Aが常に最大値以上になる。そのため、その時点までは地道に計算し、さらに残り回数がNの倍数になるように調整すると、その順番と最終的な順番が等しくなる。

a.Sort();
if (A > 1)
{
    // 最大と最小の差が十分小さくなり、かつ残り回数がNの倍数になるまでは手順通り行う
    while (a[0] * A < a[N - 1] && B > 0)
    {
        a[0] *= A;
        a.Sort();
        B--;
    }
    var num = B % (UInt64)N;
    for (UInt64 i = 0; i < num; i++)
    {
        a[0] *= A;
        a.Sort();
        B--;
    }

    //あとは順番は変わらないので、残った分を均等にModPow()する
    var pow = B / (UInt64)N;
    for (int i = 0; i < N; i++)
    {
        a[i] %= mod;    //これがないとオーバーフロー
        a[i] *= ModPow(A, pow, mod);
    }
}
foreach (var ai in a) Console.WriteLine(ai % mod);


ARC050
A: 大文字と小文字 - AtCoder Regular Contest 050 | AtCoder

大文字と小文字が与えられたとき、同じアルファベットか判定せよ。

両方大文字に変換して比較。

Console.WriteLine(Console.ReadLine().Split().Select(s => s.ToUpper()).Distinct().Count() == 1 ? "Yes" : "No");


B: 花束 - AtCoder Regular Contest 050 | AtCoder

赤い花がR本、青い花がB本ある。x本の赤い花と1本の青い花、もしくは1本の赤い花とy本の青い花からなる花束を作るとき、作ることのできる花束の個数の最大値を求めよ。すべての花を使い切る必要はない。
1 <= R, B <= 10^18
2 <= x, y <= 10^19

k本の花束を作ることができるかで二分探索する。条件は
・赤い花と青い花がk本ずつ必要
・(赤い花の本数-k) / (x-1) + (青い花の本数-k) / (y-1) がk-1以上
となる。分母が-1になるのは、すでに各花束に1本ずつ割り当て済であるため。

Int64 left = 0;
Int64 right = Math.Max(r, b);
while (right - left > 1)
{
    var m = (left + right) / 2;
    if(m <= r && m <= b && (r - m) / (x - 1) + (b - m) / (y - 1) >= m)
        left = m;
    else
        right = m;
}
Console.WriteLine(left);

条件がうまく出せずに苦しんだ。こういったときは、きれいな判定文は諦めて、さっさと泥臭く書いたほうが時間を無駄にせずに良さそうだ。


C: LCM 111 - AtCoder Regular Contest 050 | AtCoder

数字の1をA個並べた数xと、B個並べた数yがある。xとyの最小公倍数をMで割った余りを求めよ。
1 <= A, B <= 10^18
2 <= M <= 10^9

以下のサイトが分かりやすかった。
AtCoder Regular Contest 050 C - LCM 111 - pekempeyのブログ

1がA個並んだ数と1がB個並んだ数のGCDは、1がGCD(A,B)個並んだ数と等しくなる。LCMの公式により、
 LCM(x, y) = x * y / 1がGCD(A,B)個並んだ数
になる。逆元をそのまま計算できないので、xまたはyどちらかに (1 / 1がGCD(A,B)個並んだ数)を掛けてから算出する。
 LCM(x, y) = x * (y / 1がGCD(A,B)個並んだ数)
上記の掛け算の左右どちらも、111...または100100...といった形なので、等比数列の和で求めることができる。
なお、等比数列の和は、rを分母に使う普通の公式(S=a(1-r^n)/(1-r))は使えない。

static void Main(string[] args)
{
    var str = Console.ReadLine().Split();
    var a = Int64.Parse(str[0]);
    var b = Int64.Parse(str[1]);
    var mod = Int64.Parse(str[2]);

    var gcd = Gcd(a, b);

    var ret = ModPowSum(10, b, mod);
    ret *= ModPowSum(ModPow(10, gcd, mod), a / gcd, mod);
    Console.WriteLine(ret % mod);
}

static Int64 Lcm(Int64 a, Int64 b)
{
    return (a * b) / Gcd(a, b);
}

static Int64 Gcd(Int64 a, Int64 b)
{
    if (a < b)
    {
        var tmp = a;
        a = b;
        b = tmp;
    }
    if (b == 0) return a;
    var p = a > b ? a : b;
    return Gcd(b, p % b);
}

static public Int64 ModPow(Int64 x, Int64 n, Int64 mod)
{
    Int64 ret = 1;
    while (n > 0)
    {
        if ((n & 1) == 1) ret = ret * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return ret;
}

//等比数列の和を逆元なしで求める。O(Logn)。
public static Int64 ModPowSum(Int64 r, Int64 n, Int64 mod)
{
    if (n == 0) return 0;
            
    //nが奇数:1 + r + ... + r^(n-1) = 1 + r(1 + r + ... + r^(n-2))
    if (n % 2 == 1) return (ModPowSum(r, n - 1, mod) * r + 1) % mod;

    //nが偶数:1 + r + ... + r^(n-1) = ( 1 + r + ... + r^(n/2-1)) +  r^(n/2) x ( 1 + r + ... + r^(n/2-1))
    Int64 result = ModPowSum(r, n / 2, mod);
    return (result * ModPow(r, n / 2, mod) + result) % mod;
}

自力で頑張ってみたが、等比数列の和でやはり躓いてしまった。上の関数はライブラリに入れておく。


ARC049
A: "強調" - AtCoder Regular Contest 049 | AtCoder

文字列Sと、整数A,B,C,Dが与えられる。SのA,B,C,D文字目の後ろに"を挿入した文字列を出力せよ。

そのまま実装する。LINQ便利。

Console.WriteLine(s.Insert(a, "\"").Insert(b + 1, "\"").Insert(c + 2, "\"").Insert(d + 3, "\""));


B: 高橋ノルム君 - AtCoder Regular Contest 049 | AtCoder

二次元座標にN人の人がいて、それぞれ位置(x,y)が与えられる。また、各人に定数ciが割り当てられており、その人がある点(X,Y)に移動するには、ci * Max(|xi - X|, |yi - Y|)の時間がかかる。全員が一点に集まるのに必要な、最小の時間を求めよ。
2 <= N <= 1000

  • 10^5 <= xi, yi <= 10^5

1 <= ci <= 1000

「ある時間tで一点に集まることができるか」で二分探索。各人が時間tで行ける範囲の四角形がすべて重なればOK。

static void Main(string[] args)
{
    /// 入力は省略

    double l = 0;
    double r = 100000 * 1000 + 1;
    while (r - l > 0.0001)
    {
        var m = (l + r) / 2.0;
        if (Possible(m, x, y, c)) r = m;
        else l = m;
    }
    Console.WriteLine(r);
}

static bool Possible(double t, double[] x, double[] y, double[] c)
{
    double x1 = -100001;
    double y1 = -100001;
    double x2 = 100001;
    double y2 = 100001;

    for (int i = 0; i < x.Length; i++)
    {
        if (x[i] - t / c[i] > x2) return false;
        if (y[i] - t / c[i] > y2) return false;
        if (x[i] + t / c[i] < x1) return false;
        if (y[i] + t / c[i] < y1) return false;

        x1 = Math.Max(x1, x[i] - t / c[i]);
        y1 = Math.Max(y1, y[i] - t / c[i]);
        x2 = Math.Min(x2, x[i] + t / c[i]);
        y2 = Math.Min(y2, y[i] + t / c[i]);
    }

    return true;
}


C: ぬりまーす - AtCoder Regular Contest 049 | AtCoder

N頂点の有向グラフの頂点に着色していく。ただし、頂点間には次のタイプの制約がいくつかある。
タイプ1:ある頂点xに塗るとき、すでに頂点yに塗られていなければならない
タイプ2:ある頂点uに塗るとき、すてに頂点vに塗られていてはならない
タイプ1がA個、タイプ2がB個存在する。着色できる頂点の最大数を求めよ。
1 <= N <= 100
0 <= A <= 100
0 <= B <= 10

タイプ1のみで考えると、y→xの有効グラフについて、閉ループを構成するノードが着色できない。そして、着色不可のノードの下流もすべて着色できない。
タイプ2は
 uに着色するとき:uに塗るときは、すでにvに塗られていなければならない
 uに着色しないとき:uを着色不可とする
と言い換えることができ、これはタイプ1と同じ制限となる。
タイプ2は2^10パターンしかないので、全列挙して最大数を求めればよい。また、閉ループの検出は強連結成分分解を使うと効率が良い。

var ret = 0;
var scc = new Scc(); //強連結成分分解のクラス(個人ライブラリ)
for (int mask = 0; mask < 1 << typeBs.Count(); mask++)
{
    scc.Init(n);

    foreach (var typeA in typeAs) scc.AddEdge(typeA.Item2, typeA.Item1);    //y -> x のエッジを足す

    var notUse = new bool[n];
    for (int i = 0; i < typeBs.Count(); i++)
    {
        var typeB = typeBs[i];
        if ((mask >> i) % 2 == 1)
            scc.AddEdge(typeB.Item1, typeB.Item2);  //u -> v のエッジを足す
        else
            notUse[typeB.Item1] = true;             //uを使用禁止にする
    }

    scc.GetScc(); //強連結成分分解

    //閉ループ内のノードは使用できない
    for (int sccIdx = 0; sccIdx < scc.SComponents.Count(); sccIdx++)
    {
        if (scc.SComponents[sccIdx].Count() > 1) //連結成分内のノード個数が1以上=閉ループ
        {
            foreach (var comp in scc.SComponents[sccIdx])
                notUse[comp] = true;
        }
    }

    //使用禁止ノードの下流はすべて使用できない
    for (int j = 0; j < n; j++)
        for (int i = 0; i < n; i++)
            if (notUse[i]) foreach (var child in scc.E[i]) notUse[child] = true;

    ret = Math.Max(ret, notUse.Count(v => !v));
}

Console.WriteLine(ret);

HackerRank Week of Code - 23 参加日記

281位/10162人。たぶん実際の参加者はこの半分くらいか。
難易度Easyの2問は省略する。


Treasure Hunting

二次元平面上の(0,0)地点にいる人が、地点(x,y)にたどり着きたい。彼はおかしなマシンを持っていて、このマシンは①ある地点(x,y)から(x,y)+k(a,b)へ飛び、②さらにその地点(x,y)から(x,y)+n(a',b')で飛ぶことができる。ここで、(a',b')はベクトル(a,b)を反時計回り方向に直行させた、同じ長さのベクトルとする。a,b,x,yが与えられたとき、目的地に着くためのkとnを求めよ。
1<=x,y,z,b<=10^9

(0,0)→(a,b)→(x,y)が90度になる点を見つければよく、これは図にすれば簡単に求められる。

var k = ((a / b) * x + y) / (b / a + a / b);
Console.WriteLine(k / a);           //k
Console.WriteLine(-(x - k) / b);    //n


Unexpected Problem

英小文字からなる文字列sと整数mが与えられる。このとき、
 1 <= tの長さ <= m
 s・t = t・s
を満たす文字列tがいくつあるか答えよ。
1 <= |s| <= 5 x 10^5
1 <= m <= 2 x 10^9

たとえば文字列sが
abcabc
なら、tになり得るのは
abc・abcabc
の2つになる。文字列sが
ababab
なら、tになり得るのは
ab・abab・ababab
の3つ。つまり、sの中で、abcのように繰り返される文字列の長さの最小値でmを割ればよい。

static void Main(string[] args)
{
    var mod = 1000000007;
    var s = ReadLine().ToCharArray();
    var m = Int64.Parse(Console.ReadLine());

    var packetSize = GetPacketSize(s);

    Console.WriteLine((m / packetSize) % mod);
}

static Int64 GetPacketSize(char[] s)
{
    for (Int64 i = 1; i <= s.Length / 2; i++)
    {
        if (Satisfies(i, s)) return i;
    }
    return s.Length;
}

static bool Satisfies(Int64 size, char[] s)
{
    if (s.Length % size != 0) return false;
    for (Int64 i = size; i < s.Length; i += size)
    {
        for (Int64 j = 0; j < size; j++)
        {
            if (s[j] != s[i + j]) return false;
        }
    }
    return true;
}


Gravity Tree

ノード数n(番号1..n、1が根)の木があり、ノード間の距離は1である。あるノードのスイッチがONのとき、そのサブノードもすべてスイッチがONになる。スイッチONのノードからは特別なパワーが発生し、その量は各ノードuに対し、(uからスイッチONノードへの距離)^2の合計になる。スイッチをONにするノードvと、ノードuが与えられたとき、uが受けるパワーの値を求めよ。ただし、クエリはq個連続して与えられる。
1<=n,q<=10^5

n,qが大きいので普通にやるとTLE。区間で処理する。
・オイラーツアーの結果をIN時間とOUT時間の配列にする
・すると、頂点vの子ノードは区間{ [in_v, out_v) }となる
・これをセグメント木で管理する
セグメント木は3つ用意し、セグメントに対して①加算した距離の合計、②加算した距離xセグメント長の合計、③加算した距離の二乗の合計、を保持するようにする。
①と②があれば③は計算で求められる。(詳しくはコード中にコメント)

これをまず、ノード1からの距離で初期化する。そして、ノード1から子ノードを再帰的に処理していくことで、すべてのノードからの情報に更新することができる。

//ノード番号は0-indexedで持つ

const int maxn = 500042;

static List<int>[] g;       //グラフ情報 (親 => 子)
static int[] que;           //クエリi番目のノードv
static List<int>[] lst;     //ノードuに関係するクエリ番号

static int[] _in;           //DFS時の各ノードのIN時間
static int[] _out;          //DFS時の各ノードのOUT時間

//セグメント木の接点の値(3種類)
static Int64[] _add;
static Int64[] _sum1;
static Int64[] _sum2;

static Int64[] ans;

static void Main(string[] args)
{
    var n = Int32.Parse(Console.ReadLine());

    //変数の初期化
    g = Enumerable.Range(0, maxn).Select(v => new List<int>()).ToArray();
    _in = new int[maxn];
    _out = new int[maxn];
    _add = new Int64[4 * maxn];
    _sum1 = new Int64[4 * maxn];
    _sum2 = new Int64[4 * maxn];
    ans = new Int64[maxn];

    //ノード情報を取得
    var nodeInfo = ReadLine().Split().Select(v => Int32.Parse(v)).ToArray();
    for (int i = 1; i < n; i++)
    {
        int p = nodeInfo[i - 1];
        g[p - 1].Add(i);
    }

    //ルートからの距離で、_in[]・_out[]・_add[]・_sum1[]・_sum2[]を作成する
    dfs1();

    //クエリ情報を取得
    var q = Int32.Parse(Console.ReadLine());
    que = new int[q];
    lst = Enumerable.Range(0, maxn).Select(v => new List<int>()).ToArray();

    for(int i = 0; i < q; i++)
    {
        var str = Console.ReadLine().Split();
        var u = Int32.Parse(str[0]) - 1;
        var v = Int32.Parse(str[1]) - 1;
        que[i] = v;
        lst[u].Add(i);
    }

    // uごとに解を作成
    dfs2();

    for (int i = 0; i < q; i++)
        Console.WriteLine(ans[i]);
}

static int t = 0;
static void dfs1(int v = 0, int h = 0)
{
    _in[v] = t++;
    upd(_in[v], _in[v] + 1, h);
    foreach(var u in g[v])
        dfs1(u, h + 1);
    _out[v] = t;
}

// 区間[a, b)をvalでインクリメントする
// vはセグメント木の接点番号で、接点は[l,r)に対応づいている
//   _add[v]: 接点vの区間に対して加算されたvalの合計
//   _sum1[v]: 接点vの区間に対して加算されたvalの総合計(_add x 区間の長さ)
//   _sum2[v]: 接点vの区間に対して加算されたval^2の合計
static void upd(int a, int b, int val, int v = 1, int l = 0, int r = maxn)
{
    if (a <= l && r <= b)
    {
        _add[v] += val;
        _sum2[v] += 2 * _sum1[v] * val + (r - l) * val * val;   //例:(a+k)^2 + (b+k)^2 = (a^2 + b^2) + 2 (a+b) k + 2 k^2
        _sum1[v] += (r - l) * val;
        return;
    }
    if (r <= a || b <= l)
        return;
    int m = (l + r) / 2;
    upd(a, b, val, 2 * v, l, m);
    upd(a, b, val, 2 * v + 1, m, r);
    _sum1[v] = _sum1[2 * v] + _sum1[2 * v + 1];
    _sum2[v] = _sum2[2 * v] + _sum2[2 * v + 1];
    _sum2[v] += 2 * _sum1[v] * _add[v] + (r - l) * _add[v] * _add[v];
    _sum1[v] += (r - l) * _add[v];
}

// ノードuに関連する解を求める
// 全ノード分を再帰的に
static void dfs2(int u = 0)
{
    // 自分以下のサブノードを-1、それ以外を+1すると自分からの距離になる
    // ひとつひとつやると遅いので、再帰的に全ノードでやってしまう
    foreach(var node in g[u])
    {
        upd(0, maxn, 1);
        upd(_in[node], _out[node], -2);
        dfs2(node);
        upd(0, maxn, -1);
        upd(_in[node], _out[node], 2);
    }

    // ノードuに関連する解を保持
    foreach (var q in lst[u])
    {
        // vのIN時間・OUT時間から、その区間までの距離^2の合計を求める
        ans[q] = get(_in[que[q]], _out[que[q]]).Item1;
    }
}

// 区間[a,b)の結果を返す
//  Item1: 値の2乗の合計
//  Item2: 値の合計
static Tuple<Int64, Int64> get(int a, int b, int v = 1, int l = 0, int r = maxn)
{
    if(a <= l && r <= b)
        return new Tuple<Int64, Int64>(_sum2[v], _sum1[v]);
    if(r <= a || b <= l)
        return new Tuple<Int64, Int64>(0, 0);
    int m = (l + r) / 2;
    var A = get(a, b, 2 * v, l, m);
    var B = get(a, b, 2 * v + 1, m, r);
    var C = new Tuple<Int64, Int64>(A.Item1 + B.Item1, A.Item2 + B.Item2);
            
    //区間[a,b)部を足す
    //Cだけだと、[a, m)合計+[m, b)合計なので足りない
    int inter = Math.Min(r, b) - Math.Max(l, a);
    var r_first = C.Item1 + 2 * C.Item2 * _add[v] + inter * _add[v] * _add[v];
    var r_second = C.Item2 + inter * _add[v];

    return new Tuple<Int64, Int64>(r_first, r_second);
}

セグメント木を利用する問題は初めてだった。じっくりEditorialのコードを読んで何とか理解できた。

Topcoder SRM696 参加日記

0完でレート微増だった。Div1のEasyはなぜこんなに難易度が高いのだろう(解けてない人が多すぎる)。

Div1 Easy Gperm

頂点数50、辺の数が1~20の無向グラフがある。初期状態では、いずれの頂点も着色されていない。着色には、両端が着色されている辺の数と等しいコストがかかる。すべての頂点を着色するときの最小コストを算出せよ。

辺の数が20と小さいので、辺の情報でbitDPを行う。
 dp[mask]:辺集合maskを作るための最小コスト
とし、maskはビット1の辺がコストに関係しないもの(両端が着色されていな辺)と定義する。
そして、問題とは逆に、すべてが着色された状態(mask=0、コスト=0)から、色を消すごとにコストを追加していくとうまくいく。

public int countfee(int[] x, int[] y) 
{
    var m = x.Length;

    //各頂点につながるエッジの情報(頂点番号->マスク)
    var v_mask = new int[50];
    for (int i = 0; i < m; i++)
    {
        v_mask[x[i]] |= 1 << i;
        v_mask[y[i]] |= 1 << i;
    }

    //無効なエッジの集合を作るための最小コスト(無効なエッジ:コストに絡まないもの)
    var dp = new int[(1 << m)];
    for (int i = 0; i < (1 << m); i++) dp[i] = Int32.MaxValue / 3;

    //すべてのエッジが有効な状態(全頂点がPaintされている状態)から色を消していく
    dp[0] = 0;
    for (int mask = 0; mask < 1 << m; mask++)
    {
        //それぞれの頂点の色を消してみる
        for (int v = 0; v < 50; v++)
        {
            var mask_erased = mask | v_mask[v];  //色を消した頂点に関連するエッジすべて
            dp[mask_erased] = Math.Min(dp[mask_erased], dp[mask] + m - BitCount(mask));
        }
    }

    return dp[(1 << m) - 1];
}

static int BitCount(int i)
{
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

HackerRank Week of Code - 22 参加日記

457位/12356人。Mediumを1つ落としたのが痛い。いつも数学がネックだ・・・。

Cookie Party

n人のゲストとm枚のクッキーがある。ゲスト全員に同じ枚数を配るためには、あと何枚クッキーを焼けばよいか。

クッキーがすでに足りているか、足りない場合はゲストの人数で割り切れるかで場合分けする。

if (m <= n)
    Console.WriteLine(n - m);
else if (m % n == 0)
    Console.WriteLine(0);
else
    Console.WriteLine((m / n + 1) * n - m);


Making Polygons

N本の棒aiが与えられる。この棒すべてを使って非退化な多角形を作るには、最少で何本の棒を折る必要があるか。折れた棒は両方使うものとする。
1 <= N <= 50
1 <= ai <= 100

「非退化」というのは重なった辺も、長さゼロの辺も存在しないという意味で、要するに普通の多角形のことらしい。
1本しかないときは、当然2回折らないと多角形にならない。
2本のときは、同じ長さなら2回、違う長さなら1回折る必要がある。
3本以上あるなら、
 最長の辺の長さ > それ以外の辺の長さ合計
に合致したら一本も折らなくてよい。それ以外のときは、最長のものを1回折る必要がある。

if (a.Count() == 1)
{
    Console.WriteLine(2);
}
else if (a.Count() == 2)
{
    if (a[0] == a[1]) Console.WriteLine(2);
    else Console.WriteLine(1);
}
else
{
    if (a[0] < a.Sum() - a[0]) Console.WriteLine(0);
    else Console.WriteLine(1);
}

理屈は簡単だが、棒が1本からという条件を見逃すとWAになってしまう。


Matching Sets

2つの整数の集合{ X = { x_0, x_1, ..., x_{n-1} }, Y = { y_0, y_1, ..., y_{n-1} } }があり、次のオペレーションが可能である。
1、XとYから1つずつ{ x_i, x_j }を選ぶ({ i \neq j})
2,{ x_i }から1引き、 { y_j }に1足す
XとYが等しくなる最少のオペレーション回数を求めよ。不可能な場合は-1を返すこと。

XとYの合計が異なるときは不可能。それ以外であれば、両方ともソートして1つずつオペレーションしていけば良い。

if (x.Sum() != y.Sum())
{
    Console.WriteLine(-1);
    return;
}

Int64 diff = 0;
x.Sort();
y.Sort();
for (int i = 0; i < n; i++)
{
    diff += Math.Abs(x[i] - y[i]);
}

Console.WriteLine(diff / 2);


Number of Sequences

整数の数列が次を満足するとき、この数列はNiceであるとする
{ 0 \leq a_k \leq k - 1 }
{ a_k \equiv\,a_m\,mod\,k } (kがmを割り切るすべてのk,mのペアについて)
一部が-1になっている整数の数列nが与えられる。-1に任意の数字を入れたとき、この数列がNiceになる組み合わせ数を求めよ。

まず、a[i]の添え字がa[p*q]と素因数分解できるものは無視することができる(中国の剰余定理によりa[p]とa[q]が決まっていれば、a[p*q]が一意に求まるため)。
そして、各素数のべき乗番目
 a[p], a[p^2], a[p^3] ... (p^x < n)
について、値が-1ならp通りのパターンがある。
たとえば、p=3のとき
 a[3]: -1 → あり得る値は { 0, 1, 2 }
 a[9]: -1 → あり得る値は { 0, 1, 2, 3, 4, 5, 6, 7, 8 }
となるが、a[3]でどの値が選ばれたとしても、a[9]で候補になる数は9/3=3個になる。

static void Main(string[] args)
{
    Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false });

    var n = Int32.Parse(Console.ReadLine());
    var a = ReadLine().Split().Select(s => Int32.Parse(s)).ToList();
    a.Insert(0, 0);

    //それぞれのkの因数すべて
    var facts = Enumerable.Range(0, n + 1).Select(v => new List<int>()).ToList();
    for (var k = 2; k <= n; k++)
    {
        Int64 kp = k;
        while (kp <= n)
        {
            facts[(int)kp].Add(k);
            kp += k;
        }
    }

    //すでに値がある場合に、Niceに違反する因数がないかチェック
    for (var k = n; k > 0; k--)
    {
        if (a[k] == -1) continue;
        foreach (var fact in facts[k])
        {
            if (a[fact] != -1 && a[fact] != a[k] % fact)
            {
                Console.WriteLine(0);
                Console.Out.Flush();
                return;
            }
            a[fact] = a[k] % fact;
        }
    }

    //kが(素数^p)で表せるならその素数を取得
    var basePrimes = new int[n + 1];
    for (var k = 2; k <= n; k++)
    {
        if (facts[k].Count() == 1)
        {
            Int64 kp = k;
            while (kp <= n)
            {
                basePrimes[kp] = k;
                kp *= k;
            }
        }
    }

    //a[k]が-1かつkが(素数^p)で表せるなら、k通りの組み合わせがある
    Int64 ret = 1;
    for (var k = 2; k <= n; k++)
    {
        if (a[k] == -1 && basePrimes[k] != 0)
        {
            ret *= (Int64)basePrimes[k];
            ret %= 1000000007;
        }
    }

    Console.WriteLine(ret);
    Console.Out.Flush();
}


Submask Queries

要素数nの集合{ U = {1, 2, ..., n} }がある。Uの部分集合すべてに値0が代入されている。
この時、次の3タイプのクエリが渡される。
1. 1 x S:集合Sの部分集合すべてに値xを代入する
2. 2 x S:集合Sの部分集合すべての値についてxとXORをとる
3. 3 s:集合Sの値を出力する
クエリが連続で与えられるので、出力値を求めよ。
1 <= n <= 16
1 <= m(クエリ数) <= 10^5
0 <= x <= 2^30 - 1

最後に代入された時間と値、そしてそこからのXOR合算を効率よく求める必要がある。
各クエリごとのループ回数を少なくするため、下位8ビットと上位8ビットに平方分割する。クエリ1・2では、下位8ビットのサブセットすべてについて、以下の情報を保持していく。

var set_times = new int[1 << 8, 1 << 8];   //最後に代入した時間
var set_values = new int[1 << 8, 1 << 8];  //代入された値
var xor_time = new List<ComparablePair<int, int>>[1 << 8, 1 << 8];  // XORした時間と、そこまでのXOR合算

クエリ3では、①該当ビットに最後に代入された値を取得し、②その時間をもとにそこからのXOR合算を算出する。
たとえば、
1:XOR、2:代入、3:XOR、4:代入、5:代入、6:XOR、7:XOR
だったとしたら、最終的な値は5で代入された値へ、3までのXOR合算と7までのXOR合算をXORしたものになる。この、最後の代入の直前のXORは、時間で二分探索すれば求められる。
①②いずれも、上位8ビットのスーパーセットすべて+下位8ビットのパターンで行えばよい。

static void Main(string[] args)
{
    Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false });

    var str = Console.ReadLine().Split();
    var n = Int32.Parse(str[0]);
    var m = Int32.Parse(str[1]);

    var set_times = new int[1 << 8, 1 << 8];   //last updated time to [H, L]
    var set_values = new int[1 << 8, 1 << 8];  //[H, L] => value of mask { all subsets of H } + L
    var xor_time = new List<ComparablePair<int, int>>[1 << 8, 1 << 8];  // time of xor -> xor value of mask [H, L]

    for (int i = 0; i < 1 << 8; i++)
    {
        for (int j = 0; j < 1 << 8; j++)
        {
            set_times[i, j] = -1;
            xor_time[i, j] = new List<ComparablePair<int, int>>() { new ComparablePair<int, int>(-1, 0) };
        }
    }

    for (int i = 0; i < m; i++)
    {
        str = Console.ReadLine().Split();
        var bit = 0;
        for (int j = 0; j < n; j++)
        {
            bit *= 2;
            if (str[str[0] == "1" || str[0] == "2" ? 2 : 1][j] == '1') bit += 1;
        }
        var top = bit >> 8;
        var bottom = bit & 0xFF;

        //assign
        if (str[0] == "1")
        {
            var val = Int32.Parse(str[1]);
            var sub = bottom;
            do
            {
                set_values[top, sub] = val;
                set_times[top, sub] = i;
                sub = (sub - 1) & bottom;
            } while (sub != bottom);
        }

        //xor
        if (str[0] == "2")
        {
            var val = Int32.Parse(str[1]);
            var sub = bottom;
            do
            {
                xor_time[top, sub].Add(new ComparablePair<int, int>(i, val ^ xor_time[top, sub].Last().Item2));
                sub = (sub - 1) & bottom;
            } while (sub != bottom);
        }

        //print
        if (str[0] == "3")
        {
            //find the last updated time and its time
            var val = 0;
            var lastUpdated = -1;

            //iterate supersets of top
            var comp = ~top & 0xFF;
            for (int sub = comp; ; sub = (sub - 1) & comp)
            {
                int super_top = top | sub;  //superset of top
                if (set_times[super_top, bottom] > lastUpdated)
                {
                    lastUpdated = set_times[super_top, bottom];
                    val = set_values[super_top, bottom];
                }
                if (sub == 0) break;
            }

            //apply xors from the last updated time
            comp = ~top & 0xFF;
            for (int sub = comp; ; sub = (sub - 1) & comp)
            {
                int super_top = top | sub;  //superset of top
                var tgt_xors = xor_time[super_top, bottom];

                var right_xor = tgt_xors.Last().Item2;
                var left_xor = GetLastXor_BeforeLastUpdate(tgt_xors, lastUpdated).Item2; //get the last xor-ed value before the update time using binary search

                // getting xors from i to j: xor(0..j) ^ xor(0..i-1)
                val ^= (left_xor ^ right_xor);

                if (sub == 0) break;
            }

            Console.WriteLine(val);
        }
    }

    Console.Out.Flush();
}

//return [upper_bound() - 1], xors are sorted by item1
static ComparablePair<int, int> GetLastXor_BeforeLastUpdate(List<ComparablePair<int, int>> xors, int time)
{
    var lb = -1;
    var ub = xors.Count();

    while (ub - lb > 1)
    {
        var mid = (lb + ub) / 2;
        if (xors[mid].Item1 <= time)
        {
            lb = mid;
        }
        else
        {
            ub = mid;
        }
    }

    return xors[lb];
}

AGC003の参加日記

2完で416位/700人くらい。AtCoderのコンテストは問題文が分かりやすくて良い(日本語だからというだけじゃなく)。


A - Wanna go back home

二次元平面上のある点から、1ターンずつ東西南北いずれかに正の距離移動する。ターンごとの方角が与えられたとき、元の位置に戻ることができるか。

東西と南北それぞれについて、たとえば東だけなどある方向に行きっぱなしになっていなければ、元の位置に戻ることができる。

if (((str.Count(c => c == 'E') == 0 && str.Count(c => c == 'W') == 0) || (str.Count(c => c == 'E') > 0 && str.Count(c => c == 'W') > 0)) &&
    ((str.Count(c => c == 'N') == 0 && str.Count(c => c == 'S') == 0) || (str.Count(c => c == 'N') > 0 && str.Count(c => c == 'S') > 0)) )
{
    Console.WriteLine("Yes");
}
else
{
    Console.WriteLine("No");
}


B - Simplified mahjong

1からNまでの整数が書かれたカードが、それぞれAi枚ある。2枚のカードについて、整数の差の絶対値が1以下ならペアを作ることができる。作ることのできるペアの最大数を求めよ。

連続したカードごとのグループを作り、グループ内のカード枚数/2を合計すればよい。

Int64 ret = 0;
Int64 sum = 0;
for (int i = 0; i < a.Count(); i++)
{
    if (a[i] == 0)
    {
        ret += sum / 2;
        sum = 0;
    }
    else sum += a[i];
}
ret += sum / 2;
Console.WriteLine(ret);


C - BBuBBBlesort!

長さNの数列Aに以下の操作を行い、昇順にソートする。
・操作1:連続する2つの要素の順番を反転
・操作2:連続する3つの要素の順番を反転
最少の操作1の回数を答えよ。
1 <= N <= 10^5
0 <= Ai <= 10^0 (整数のみ)

操作2は何回行ってもも構わないので、奇数位置にある値はコストなしで動かすことができる。よって、偶数位置にあるものを、奇数位置へ動かす回数が答えとなる。
(同じ回数だけ、奇数位置から偶数位置に動かすものがある)

var n = Int32.Parse(Console.ReadLine());
var a = new List<Int64>();
var dictIdx = new Dictionary<Int64, int>();
for (int i = 0; i < n; i++)
{
    a.Add(Int64.Parse(Console.ReadLine()));
    dictIdx.Add(a[i], i);
}

var wk = a.OrderBy(v => v).ToList();
var indices = new Int64[n];
for (int i = 0; i < wk.Count; i++)
{
    indices[dictIdx[wk[i]]] = i;
}

Int64 ret = 0;
for (int i = 0; i < a.Count(); i++)
{
    if (i % 2 == 0 && indices[i] % 2 == 1) ret++;
}
Console.WriteLine(ret);