tekiheiの日記

競技プログラミングについて書きます

ABC148 参加記録

atcoder.jp 1679->1673(-6) 厳しいですね

C. Snack

 lcm(A,B)を求めればよいです。 A \times B = gcd(A,B) \times lcm(A,B)より、 lcm(A,B)=A \times B \div gcd(A,B)です。 gcdC++であれば__gcd(組み込み関数?)を使って求められます。

#include<bits/stdc++.h>
using namespace std;

using Lint=long long;

Lint lcm(int a,int b)
{
   return (Lint)a*b/__gcd(a,b);
}

int main()
{
   int a,b; cin>>a>>b;
   cout<<lcm(a,b)<<endl;
   return 0;
}
D. Brick Break

消す個数の最小値を求める代わりに、残す個数の最大値を求めることにします。そうすると求めるものは、「 aに部分列として含まれる、 1,2,...というような列のうち、長さが最大のものの長さ」です。要するに、左から順番に 1,2,...と選んでいったとき、最大でいくつ選べるかということです。これは、次の数を選べる範囲でもっとも左から選ぶことを繰り返せばよいです。なぜならば、最も左にあるものを xとしたとき、 xを選ばないような最適解が存在するならば、 xを選ぶような最適解も存在するからです。直感的には、もっとも左にあるものを選んで損をすることはないからです。

#include<bits/stdc++.h>
using namespace std;

int main()
{
   int N; cin>>N;
   vector<int> a(N);
   for(int i=0;i<N;i++) cin>>a[i];

   int cur=0;
   for(int i=0;i<N;i++){
      if(a[i]==cur+1) cur++;
   }
   cout<<(cur==0? -1:N-cur)<<endl;
   return 0;
}

コンテスト中はDPを書きました。

#include<bits/stdc++.h>
using namespace std;

int main()
{
   int N; cin>>N;
   vector<int> a(N);
   for(int i=0;i<N;i++) cin>>a[i];

   if(count(a.begin(),a.end(),1)==0){
      cout<<-1<<endl;
      return 0;
   }

   const int MAX=*max_element(a.begin(),a.end());
   vector<bool> dp(MAX+1);
   // dp[i]:=(今見ているところまでで)1,2,...iを部分列として含んでいるか
   dp[0]=true;
   for(int i=0;i<N;i++){
      if(dp[a[i]-1]) dp[a[i]]=true;
   }
   int ans=0;
   for(int i=1;i<=MAX;i++) if(dp[i]) ans=i;
   cout<<N-ans<<endl;
   return 0;
}
E. Double Factorial

まず末尾の 0の個数というのは、 10を因数としていくつ持つかということです。因数の 10の個数は min(2の個数,5の個数)なので、これを数えればよいです。 N!が素因数 pをいくつ持つかは以下の式で計算できます。

 N!が持つ素因数 pの個数は  \ \displaystyle \lfloor \frac{N}{p} \rfloor + \lfloor \frac{N}{p^{2}} \rfloor + \lfloor \frac{N}{p^3} \rfloor+ \cdots

これはこの問題の解説動画で見た記憶があります。
もとの問題に戻ります。 Nの偶奇で場合分けをします。

  •  Nが奇数の場合
     2の個数が 0なので答えは 0です。
  •  Nが偶数の場合
     2の個数は N!の場合と同じです(奇数は 2で割れないので、無くても変わらない)。 5の個数については、 f(N) 2で何度か割ることによって (N/2)!の場合に帰着できるので求まります。
#include<bits/stdc++.h>
using namespace std;

using Lint=long long;

Lint calc(Lint N,Lint p)
{
   // N!が素因数pをいくつ含むか
   Lint res=0;
   while(N/p>0){ res+=N/p; N/=p; }
   return res;
}

Lint func(Lint N)
{
   if(N<=1) return 0;
   if(N%2==1) return 0;
   else{ return min(calc(N,2),calc(N/2,5)); }
}

int main()
{
   Lint N; cin>>N;
   cout<<func(N)<<endl;
   return 0;
}

上の実装では N p^{k}で割った結果と、 N p k回割った結果が等しいことを利用しています。これは p進法で考えると分かります(前者が k個右シフトしていて、後者は 1個の右シフトを k回しているため)。実際に p^{k}を計算してオーバーフローしたことがあるので、こうしています。

F. Playing Tag on Tree

めっちゃ解かれているのでおそらくdfsするだけでいいんだろうなぁと思いました(最悪)。直感的には追いつかれないところで一番遠いところに逃げるのが最適だろうと分かりますが、、、

#include<bits/stdc++.h>
using namespace std;

using Graph=vector<vector<int>>;

int main()
{
   int N,T,A; cin>>N>>T>>A;
   T--; A--;
   Graph G(N);
   for(int i=0;i<N-1;i++){
      int a,b; cin>>a>>b;
      a--; b--;
      G[a].push_back(b);
      G[b].push_back(a);
   }
   vector<int> dist(N);
   function<void(int,int)> dfs=[&](int v,int p)
   {
      for(int u:G[v]) if(u!=p){
         dist[u]=dist[v]+1;
         dfs(u,v);
      }
   };

   vector<int> distT(N);
   dist[T]=0; dfs(T,-1); distT=dist;
   vector<int> distA(N);
   dist[A]=0; dfs(A,-1); distA=dist;
   int ans=-1;
   for(int i=0;i<N;i++) if(distT[i]<distA[i]){
      ans=max(ans,distA[i]-1);
   }
   cout<<ans<<endl;
   return 0;
}

感想

ちゃんと理解したら別の記事に書こうと思います。今回はみんな速すぎてビックリしました。