競プロをしながら、節約と株式投資でセミリタイアを目指す東大卒のブログ

東大卒でメーカー勤務の私がセミリタイアするために投資や競プロを頑張っていこうという趣旨で始めたブログです。独身男性です。お金の大切さや今後の生き方も併せて伝えられたらと思います。

ABC170で書いたコード

using System;
using System.Numerics;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace debug
{
    class main
    {
        static void Main(string args)
        {
            //問題クラスを展開
            ProgramE a = new ProgramE();
            a.main();//実行する

        }

    }
    //ABC170
    class ProgramA
    {
        public void main()
        {
            //入力
            string s = Console.ReadLine().Split(' ');

            //入力が0ならその位置を答で出す
            for (int i = 0i < 5i++)
            {
                if (s[i] == "0")
                    Console.WriteLine(i + 1);
            }

        }
    }

    class ProgramB
    {
        public void main()
        {
            //入力
            string s = Console.ReadLine().Split(' ');
            int a = int.Parse(s[0]);
            int b = int.Parse(s[1]);


            //奇数ならあり得ない
            if (b % 2 == 1)
            {
                Console.WriteLine("No");
                return;
            }

            //aの2倍以上4倍以下なら答え
            if (a * 2 <= b && b <= 4 * a)
                Console.WriteLine("Yes");
            else
                Console.WriteLine("No");


        }
    }

    class ProgramC
    {
        public void main()
        {

            //入力
            string s = Console.ReadLine().Split(' ');
            int x = int.Parse(s[0]);
            int n = int.Parse(s[1]);

            string a = Console.ReadLine().Split(' ');
            int dif = new int[100];

            for (int i = 0i < ni++)
                dif[int.Parse(a[i]) - 1]++;

            long ans1 = 0;
            long ans2 = 0;

            //101まででxとの差が一番小さいものを見つける
            for (int i = x - 1i <= 100i++)
            {
                if (i == 100)
                {
                    ans1 = 101;
                    break;
                }

                if (dif[i] == 0)
                {
                    ans1 = i + 1;
                    break;
                }
            }

            //0まででxとの差が一番小さいものを見つける
            for (int i = x - 1i >= -1i--)
            {
                if (i == -1)
                {
                    ans2 = 0;
                    break;
                }

                if (dif[i] == 0)
                {
                    ans2 = i + 1;
                    break;
                }
            }

            //もし絶対量が同じなら小さい方、それ以外は絶対値の小さい方を出す
            if (ans1 == ans2)
                Console.WriteLine(ans1);
            else if *1
                Console.WriteLine(ans1);
            else
                Console.WriteLine(ans2);

        }
    }

    class ProgramD
    {
        public void main()
        {

            //入力
            long n = long.Parse(Console.ReadLine());
            string s = Console.ReadLine().Split(' ');
            long a = new long[n];


            for (int i = 0i < ni++)
                a[i] = long.Parse(s[i]);

            //n=1のときは例外処理
            if (n == 1)
            {
                Console.WriteLine(1);
                return;
            }

            //ソートする
            Array.Sort(a);

            //1があれば例外処理
            if (a[0] == 1)
            {
                if (a[0] == a[1])
                    Console.WriteLine(0);
                else
                    Console.WriteLine(1);
                return;
            }

            //この整数は可能かどうかを見る
            long flag = new long[a[n - 1]];

            //同じ数があるか探す
            for (int i = 0i < n - 1i++)
            {
                if (a[i] == a[i + 1])
                {
                    flag[a[i] - 1] = 2;
                }
            }

            //それぞれの数で残るか検討
            for (int i = 0i < ni++)
            {
                //すでに数えたものは無視
                if (flag[a[i] - 1] == 1)
                    continue;

                //数えていないものはその倍数の可能性をつぶす
                long t = a[i];
                t += a[i];
                while (t <= a[n - 1])
                {
                    flag[t - 1] = 1;
                    t += a[i];
                }

            }

            //判定
            long ans = 0;
            for (int i = 0i < ni++)
            {
                //それぞれのAに対して0のものだけは成立する
                if (flag[a[i] - 1] == 0)
                    ans++;
            }

            //答え出力
            Console.WriteLine(ans);
        }
    }

    class ProgramE
    {

        //static List<long> list = new List<long>[200001];
        static SortedSet<long> list = new SortedSet<long>[200001];

        public void main()
        {
            //入力
            string s = Console.ReadLine().Split(' ');
            long n = long.Parse(s[0]);
            long q = long.Parse(s[1]);

            long a = new long[n];//レート
            long b = new long[n];//所属幼稚園
            

            for (int i = 0i < 200001i++)
                list[i] = new SortedSet<long>();

            //とりあえず園児に対して所属幼稚園とレートを読む
            for (int i = 0i < ni++)
            {
                string t = Console.ReadLine().Split(' ');
                a[i] = long.Parse(t[0]);
                b[i] = long.Parse(t[1]);
                list[b[i]].Add((a[i] << 32) + i);//幼稚園児ごとにIDで分けておかないと全部消える
            }


            //答候補入れる
            for (int i = 1i < 200001i++)
            {
                if (list[i].Count != 0)
                    list[0].Add(list[i].Max);
            }


            //転園していく
            for(int i = 0;i < q;i++)
            {
                string x = Console.ReadLine().Split();
                long c = long.Parse(x[0]);
                long d = long.Parse(x[1]);

                //移動前の最大値を削除
                list[0].Remove(list[b[c - 1]].Max);
                //移動後の幼稚園も削除
                if(list[d].Count != 0)
                    list[0].Remove(list[d].Max);
                //移動する前のその位置を削除する
                list[b[c - 1]].Remove*2;
            }

        }

        
    }
 
}





*1:ans1 - x) < (x - ans2

*2:a[c -1] << 32) + c -1);

                //移動後の幼稚園に追加する
                list[d].Add((a[c -1] << 32) + c -1);
                //移動後の幼稚園の最大値も追加
                 list[0].Add(list[d].Max);
                //移動する前の最大値を追加
                if(list[b[c - 1]].Count != 0)
                    list[0].Add(list[b[c - 1]].Max);

                //園児の所属を更新
                b[c - 1] = d;

                //最小値を出力
                Console.WriteLine((list[0].Min >> 32

競技プログラミング~ABC170後半~

おはようございます。しほみんです。

 

前回の続きで今回はD問題とE問題を振り返ります。

大体1週間遅れになって話していますが、微妙に理由があります。

・自分できちんと咀嚼して理解する時間が必要である。

・1週間おいても解法が思いつくかどうか確認するため。

 

結局仕事があるとなかなか競プロにも力を入れられないのでこんな形をとってます。まあ、早く解けるようになるとかは置いておいて引き続き頑張ってはいこうかなあと思います。

 

 

さて問題に移ります。

D問題

 長さNの数列Aがあります。

次の性質を満たすi(1<=i<=N)の数をこたえてください。

・i ≠ j である任意のjにおいてAiはAjで割り切れない。

 

 ちなみに任意という表現ですが、すべてのという意味です。なので、1つでもその数以外を考えたとき割り切れれば満たしません。

 

まあ普通に考えると、

全部の通りを見て約数かどうか判定する方法があります。

ただ、すべての組み合わせをやったらo(Nの2乗)で間に合いません。

確実に時間オーバーです。これをどうするか考えないとなりませんが、結局その考えにとらわれて解けませんでした。

 

もうちょっと考察を進めてみます。

AiよりAjが大きいときを考えてみましょう。このとき、AiはAjで割り切れないのは確かです。よって、Ai >Ajのときのみ考えればよいとわかります。

 

そこからソートして小さい順に並べれば、その数より小さいものを見れば何となくできそうです。しかし、これもo(Nの2乗)なので意味ないのです。

 

ここで、C問題がヒントになります。C問題では最初にダメな数をあらかじめ保存しておいてそのあと条件を満たす最小の数を探していました。

これと同じことをしてしまえばよいのです。つまり、大きい数から満たさない数を探すのではなく、小さい方の数から考えます。問題の条件的に小さい数の倍数となる大きい数は絶対に満たしません。(なぜなら大きい数から見たらその小さい数で割り切れてしまうから)。ということで小さい数から考えていくと解けることが分かります。

 

つまりこういうことです。

1.ソートして小さい順に並べます。

2.左からその数の倍数となるモノをすべて別の配列で記録しておく。

3.読んで、不適合であれば、その数は省いていっていく、そうでないならその倍数となるモノを記録していく。

4.このうち残った数だけを足して答えを出す。

 

ソートをすることで、選んだ数より右側の数字は等しいか大きく、選んだ数より右側は選んだものの約数になりえるからです。

つまり、

基本的な発想は

1. 左から見ていったときに、何かしらの別情報に変換する

2. 該当するものを見たとき、前の保持情報から条件を満たすか確認。満たすときは、情報を追加する。満たさないときは無視する。

3. 確認し終わったら結果を出す。

です。

 

約数倍数うんぬんよりどっちかというと計算量を減らすために、プログラム的にどう情報を残しておくかって感じの問題だったなあって感じです。

1の意識を高めると、今回みたいに該当範囲の配列保存や累積和、Union-find、木構造とかを意識して組めるようになります。

2のときにうまく出せればその情報保持は正しかったってことになります。

そういうところを確認するいい問題でしたね。

 

 

E問題

 幼稚園が200000個あります。園児がN人います。それぞれのレートはAiです。

最初園児は、Biの幼稚園にいます。それぞれの幼稚園で園児がいるとき、一番強いレートの幼稚園児のレートをその幼稚園のレートとします。

このとき、Q回の転園を行います。1回の転園でCiの園児がDiの幼稚園に転園します。

このとき、1回の転園することに、幼稚園のレートで最小のものを求めなさい。

 

最強の園児問題ですね....D躓いている時点でまあ解けなくてしょうがないのですが、覚えればよいので振り返りました。

 

さっきの基本的な考えに基づいて考えてみましょう。

まずは、与えられたデータを何かしらの構造に変えるってところを意識します。

何となく問題を見ていると、転園ごとに毎回情報を更新しないとならず、

転園するごとに、転園する園児の強さを変えて、転園前後の園の強さ、全体の園の強さの最小を更新しないとならないってことに気が付きます。

これらをo(N)のオーダーでやってしまうと当然間に合わないので、

o(logN)かo(1)でこれら処理ができないとならない構造が必要とわかります。

つまり、何かしらのデータ構造があって、それの入力、削除、最大値、最小値取り出しをo(logN)でしないとならないです。

 

ここまで考えると、できる構造は平衡二分探索木に限られるってことに気がつきます。

二分木で数の大小をうまく管理するデータ構造です(詳しくは自分も理解できてないです...が使えるようにはした)。

 

C#では、SortedSetが平衡二分探索木ということで用意されています。それを使いましょう。

(最初の準備)

・幼稚園ごとにSortedSetを用意する。(配列でやると簡単)

・幼稚園ごとに最強幼稚園児を入れておくSortedSetを用意する。

・園児の上記条件から、幼稚園ごとの情報を更新

・最強の幼稚園児たちを集めておく。

(クエリごとの対応)

・転園する前の幼稚園から選ばれた最強の園児を削除。

・転園する前の幼稚園から選ばれた園児を削除。

・転園後の幼稚園から選ばれた最強の園児を削除。

・転園後の幼稚園に選ばれた園児を入れる。

・転園する前の幼稚園から最強の園児を選びなおす。

・転園後の幼稚園から最強の園児を選びなおす。

・最強の園児たちから最弱を選ぶ。

これを素直に実装するだけです(順番間違えると破綻します。

ただし、SortedSetは、削除するとき、該当するすべての数を消します。例えば、レート34の園児が2人いたときにレート34の園児を削除するとすると両方消えます。

よって、今回は、long型をつかって、上位32ビットはレート、下位32ビットは園児番号を記録して区別します。

 

 

最後に....

このDとEは今後是非解けるようになりたい問題だなあと思います。面白いですし、こういうのアルゴリズムの勉強になります。

 

ではでは。