AtCoder BC071 C : Make a Rectangle

問題

https://beta.atcoder.jp/contests/abc071/tasks/arc081_a

解法

長方形の辺の候補となるのは2本以上存在する棒です。また、4本以上存在すれば正方形にすることが出来ます(正方形は長方形に含まれる)

 よって、同じ棒が2本現れる毎に辺の候補に追加し、その中から1番目に長いものと2番目に長いもので長方形を作れば解となります。

 なお、辺の候補が2つ以上無ければ解無しとして0を出力します。

実装

実装としては、同じ長さの棒のカウントにはmap、辺の候補の管理はvectorで行います。

#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
#include<cmath>
#include<string>

#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define REP(i,n)   FOR(i,0,n)
#define MOD 1000000007

using namespace std;
 
typedef long long int ll;

const ll INF=(ll)1e18;

int main(){
    int N;
    cin >> N;

    map<ll, int> m;
    vector<ll> a;

    REP(i,N){
        ll tmp;
        cin >> tmp;
        m[tmp]++;
        if(m[tmp] % 2 == 0){
            a.push_back(tmp);
        }
    }
    if(a.size() > 1){
        sort(a.begin(),a.end());
        cout << a[a.size()-1] * a[a.size()-2] << endl;
    }else{
        cout << 0 << endl;
    }

}

AtCoder BC070D: Transit Tree Path

問題

https://beta.atcoder.jp/contests/abc070/tasks/abc070_d

解法

xからKを経由してyに行く最短経路は、xからKへの最短経路とKからyへの最短経路の和になるので、 事前にKから各点への最短経路を求めておけば良い。

最短経路は、対象が閉路の無い木構造なので、幅優先探索深さ優先探索で求めれば良い。

なお、頂点の数Nが105でありかなり大きいため、隣接情報は隣接行列ではなく、隣接リストで行う必要がある。そうしないと、メモリ制限オーバーとなる。

実装

n = gets.chomp.to_i
edge = Array.new(n+1).map{Array.new}
(n-1).times do
    a, b, c  = gets.chomp.split(" ").map(&:to_i)
    edge[a] << [b, c]
    edge[b] << [a, c]
end

q, k = gets.chomp.split.map(&:to_i)

cost = Array.new(n+1,0)
done = Array.new(n+1,false)

queue = [k]
done[k] = true
cost[k] = 0
while(queue.size > 0)
    v = queue.pop
    
    edge[v].each do |i, j|
        if done[i] == false
            done[i] = true
            cost[i] = cost[v] + j
            queue.push(i)
        else
        end
    end
end

q.times do
    x, y = gets.chomp.split.map(&:to_i)
    puts cost[x] + cost[y]
end

AtCoder BC070 C: Multiple Clocks

問題

https://beta.atcoder.jp/contests/abc070/tasks/abc070_c

解法

T1からTNの最小公倍数を求めれよい。

3つ以上の数の最小公倍数は、最初に2つの値の最小公倍数を求め、その結果と順次最小公倍数を求めていけば良い。

実装

def gcd(a,b)
    return a if b == 0
    gcd(b,a%b)
end

def lcm(a,b)
    a * b / gcd(a,b)
end

ans = 1
n = gets.to_i
n.times do
    t = gets.to_i
    ans = lcm(ans,t)
end
puts ans

AtCoder BC 095 D: Static Sushi

問題

https://beta.atcoder.jp/contests/abc095/tasks/arc096_b

解法

寿司の数が105なので、O(N)であれば間にいそう。ということで、まずは全探索で考えてみる。

考えられるパターンは以下の4ケースに分けられる。

ケース1:時計回りに何個か寿司を食べてそのまま終了する ケース2:時計回りに何個か寿司を食べてからスタート位置に戻り、反時計回りに何個か寿司を食べる ケース3:反時計回りに何個か寿司を食べてそのまま終了する ケース4:反時計回りに何個か寿司を食べてからスタート位置に戻り、時計回りに何個か寿司を食べる

ケース3と4はケース1と2を逆方向にしただけなので、以下ではケース1と2を考える。

ケース1は最大でN個まで寿司を食べる組み合わせしかないのでO(N)で探索可能。

ケース2については、最初に時計回りにi番目まで食べるとすると、スタート位置に戻ってから残ったN-i個の寿司のどこまで食べるかでN-i通り考えられる。

これをそのまま計算するとO(N2)となってしまい部分点しかもらえない。

そこで、事前に残っている寿司がk個の場合にどこまで食べるると最大になるかをkに対して求めておく。 そうすることで、スタート地点に戻ってからの計算量をO(1)に下げることが出来るので、全体としてO(N)に収めることが出来る。

なお、寿司のカロリーについては最初に累積和を求めておき、部分和をO(1)で算出できるようにしておく。

実装

ケース3と4はケース1と2と同じ事を逆方向に実装するだけだが、vとxのベクトルを逆方向にする(vは逆順に並べ換え、xはCから引いてから逆順に並べかえ)ことでケース1と2のロジックを流用できるかなと思ったが、素直にそのまま実装してしまった。

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

#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define REP(i,n)   FOR(i,0,n)

using namespace std;

typedef long long int ll;

const ll INF=(ll)1e18;

int main(){
    int N;
    ll C;

    cin >> N >> C;

    vector<ll> x(N+1), v(N+1), vc(N+1);
    x[0] = 0;
    v[0] = 0;
    FOR(i,1,N+1)cin >> x[i] >> v[i];

    //vの累積和を求める
    vc[0] = v[0];
    FOR(i,1,N+1){
        vc[i] = vc[i-1] + v[i];
    }

    vector<ll> r(N+1);
    r[0] = 0;
    for(int i = 1; i <= N; i++){
        r[i] = max(r[i-1], vc[i]-x[i]);
    }

    vector<ll> l(N+1);
    l[N] = vc[N] - vc[N-1] - (C-x[N]);

    for(int i = N-1; i >= 1; i--){
        l[i] = max(l[i+1], vc[N]-vc[i-1]-(C-x[i]));
    }

    ll ans = 0;
    REP(i,N+1){
        //ケース1
        ll tmp = vc[i] -  x[i];
        if(tmp > ans)ans = tmp ;

        //ケース2
        if(i < N){
            tmp = vc[i] - 2 * x[i] + l[i+1];
            if(tmp > ans)ans = tmp ;
        }
    }

    for(int i = N; i >= 1; i--){
        //ケース3
        ll tmp = vc[N] -  vc[i-1] - (C - x[i]);
        if(tmp > ans)ans = tmp ;

        //ケース4
        tmp = vc[N] - vc[i-1] - 2 * (C - x[i]) + r[i-1];
        if(tmp > ans)ans = tmp ;
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 112 参戦記

いよいよ水色が目前と言うことで、気合いを入れて臨んだのですが、逆に空回りしてしまったのか、Cで詰まって爆死しました。

ちょっと反省ですね。

A - Programming Education

問題

https://beta.atcoder.jp/contests/abc112/tasks/abc112_a

解法

Nが1だったら'Hello World'と出力し、2だったらAとBを読み込んでその和を出力するだけです。

実装

n=gets.to_i
if n == 1
    puts 'Hello World'
else
    a=gets.to_i
    b=gets.to_i
    puts a+b
end

B - Time Limit Exceeded

問題

https://beta.atcoder.jp/contests/abc112/tasks/abc112_b

解法

cとtを読み込みながら、tがT以下の場合のcの最小値を求めます。

いろいろやり方があると思いますが、今回はtがT以下のcを順次配列に詰め込んでおき、最後にソートして最小値を出力するという方法をとりました。なお、配列が空の場合は'TLE'を出力。

実装

n,t=gets.chomp.split.map(&:to_i)
c = []
n.times do
    cc, tt = gets.chomp.split.map(&:to_i)
    if tt <= t
        c << cc
    end
end
if c.size > 0
    puts c.sort[0]
else
    puts "TLE"
end

C - Pyramid

問題

https://beta.atcoder.jp/contests/abc112/tasks/abc112_c

解法

今回、一番ハマってしまった問題です。どうしても1つだけテストケースが通らずに、WA連発してしまいました。

考え方としては、CxとCyが0〜100の整数という点ですね。

この組み合わせは高々101x101通りしか無いため、約104であり全探索可能です。

よって、Cx、Cyの組み合わせについて、全ての観測値が条件を満たすかどうかをチェックして、OKであれば出力して終了となります。

調査値のhは max(H-|x-Cx|-|y-Cy|, 0) であることから、h>0 の場合に推測される高さHは、

{ \displaystyle H = h+|x-Cx|+|y-Cy| }

となるので、これが残りの調査値を全て満たすかどうかを調べることになります。

ここでポイントになるのは、観測値hが0になるケースですね。

hが0に場合はHを推測することが出来ないので、最初にhが0で無い場合のxとyを使ってHを推定してから、改めて全ての調査値について h = max(H-|x-Cx|-|y-Cy|, 0) を満たすかどうかを調べるのが正解です。

最初、h=0のケースは無視して進めてしまったため、テストケースが通らずにハマってしまいました。

解説動画見ていても同じようなハマり方している人多かったみたい。

実装

N=gets.to_i
x = []
y = []
h = []

tmpx = 0
tmpy = 0
tmph = 0
N.times do
    xx,yy,hh = gets.chomp.split.map(&:to_i)
    x << xx
    y << yy
    h << hh
    if hh > 0
        tmpx = xx
        tmpy = yy
        tmph = hh
    end
end


0.upto(100) do |i|
    0.upto(100) do |j|
        hh = tmph + (tmpx - i).abs + (tmpy - j).abs

        flag = true;
        0.upto(N-1) do |k|
            if h[k] != [0, hh -  (x[k] - i).abs - (y[k] - j).abs].max
                flag = false
                break
            end
        end      

        if flag == true
            printf("%d %d %d\n", i, j, hh)
            exit
        end
    end
end

D - Partition

問題

https://beta.atcoder.jp/contests/abc112/tasks/abc112_d

解法

求める解をDとします。

Dがa1,a2,..,aNの最大公約数ということは、DはMの約数でもあります。

つまり、M = B x D となるBが存在します。

次に、BとNの関係について考えます。

BがNより小さい場合、数列aを作ることが出来ないのでNGです。 BがNに等しい場合は、数列aは全てDとなりOKです。 BがNより大きい場合が少しややこしいのですが、この場合は、N-1個のDとM-(N-1)xDで数列を作れば良いです。

よって、M/DがN以上であれば良いです。

例えば、テストケースのM=14、N=3の場合、D=2とすると2が2個と10が1個で条件を満たします。

これを全てのMの約数についてチェックして、条件を満たす最大のものをもとめることで解けます。

なお、Mの約数の探索については、1からルートMまで調べれば良いです。

何故なら、M=AxBとした場合、AがルートM以下なら対応するBはM/Aで求めることが出来るからです。

実装

n,m=gets.chomp.split.map(&:to_i)

ans = 0
1.upto(Math.sqrt(m).to_i) do |i|
    next if(m%i != 0);
    
    if m >= i * n
        ans = i if ans < i
    end

   if m >= m/i * n
        ans = m/i if ans < m/i
    end

end
puts ans

結果

Cでハマってしまったので焦ってしまい、結果的にDも解けませんでした。最初に全探索でOKということに気づくのが遅かったのと、hが0の扱いをミスってしまったのがまずかった。反省ですね。

Dは落ち着いて考えればそんなに難しく無かったんですけど、Cで時間をとられすぎて冷静に考えることが出来ませんでした。

ということで、水色目前にして初のレートダウンになりました。

水色入りは次回にお預けですね。

f:id:taka_xyz:20181027193640p:plain

AtCoder BC102 D: Equal Cut

問題

https://beta.atcoder.jp/contests/abc102/tasks/arc100_b

解法

長さNの数列の切れ目はN-1通りあるため、3つの切れ目を選ぶ組み合わせはn-1C3通りあります。Nは最大2x105であるため、全ての組み合わせを試すのは現実的ではありません。

「P、Q、R、Sの最大値と最小値の差の絶対値が最小になる」は言い換えると、「P、Q、R、Sの大きさが出来るだけ近い場合」ということです。最適なのは、数列の総和の1/4になる場合ですが、実際にはなるべく1/4に近い値を目指すことになります。

ここで数列の三つの切れ目をi1、i2、i3とおきます。

そしてi2を固定した場合を考えます。

この時、PとQ、RとSの差が最小になる切れ目がi1とi3であったとします。

f:id:taka_xyz:20181027193334p:plain

次に、i2を右に一つずらす場合を考えます。

この時、i1とi3は「そのまま」か「右にずれる」ことになります。(つまり単調増加)

そして、PとQ、RとSの差が最小になる新しいi1とi3に更新していきます。

このような処理をi1=0、i2=1、i2=3を初期状態として繰り返しながら、「P、Q、R、Sの最大値と最小値の差の絶対値が最小」になる値を求めることで解けます。

なお、配列の部分和については、累積和を事前に求めておくことで定数時間で計算できますので、全体としてはO(N)で解くことが出来ます。

実装

n = gets.chomp.to_i
a = gets.chomp.split.map(&:to_i)

# aa = 累積和
aa = [a[0]]
1.upto(n-1) do |i|
    aa << aa[-1] + a[i]
end

i1 = 0
i2 = 1
i3 = 2

ans = 10 ** 9

while(i2 < n-2) do
    while i1 < i2 - 1 do
        if (aa[i1] - (aa[i2] - aa[i1])).abs > (aa[i1+1] - (aa[i2] - aa[i1+1])).abs
            i1 += 1
        else
            break
        end
    end

    while i3 < n - 2 do
        if (aa[n-1] - aa[i3] - (aa[i3] - aa[i2])).abs > (aa[n-1] - aa[i3+1] - (aa[i3+1] - aa[i2])).abs
            i3 += 1
        else
            break
        end
    end
    pqrs = [aa[i1], aa[i2] - aa[i1], aa[i3] - aa[i2], aa[n-1] - aa[i3]]

    ans = (pqrs.max - pqrs.min) if ans > (pqrs.max - pqrs.min)


    i2 += 1
end

puts ans

AtCoder BC102 C: Linear Approximation

問題

https://beta.atcoder.jp/contests/abc102/tasks/arc100_a

解法

以下の数式の値を最小にするbをどのようにして求めるかという問題です。

{ \displaystyle abs(A_1-(b+1)) + abs(A_2-(b+1)) + ... + abs(A_N-(b+1)) }

これは、Bi = Ai - 1 とすると

{ \displaystyle abs(B_1-b) + abs(B_2-b) + ... + abs(B_N-b) }

と書き換えることがが出来ます。

これが最小になるのは、数列Bの中央値をbとした場合です。

図にしてみるとよく分かります。 (見やすくするためにBはソート済みとしています)

f:id:taka_xyz:20181027194623p:plain

f:id:taka_xyz:20181027194635p:plain

Nが奇数の場合、bがB3より大きく(小さく)なるとその分合計値が増加します。

Nが偶数の場合、B2〜B3の間から外れると、その分合計値は大きくなります。

実装

n = gets.chomp.to_i
a = gets.chomp.split.map(&:to_i)

0.upto(n-1) do |i|
    a[i] -= (i+1)
end
a.sort!

m = a[n/2]

ans = 0
0.upto(n-1) do |i|
    ans += (a[i] - m).abs
end

puts ans