Amedeo
SYMBIOSYS OF
PURPOSE & STYLE
Follow us

Search

ekusiadadus
  -  Programming   -  Competitive Programming   -  CodeForces Round #686(Div.3)

CodeForces Round #686(Div.3)

CodeForces Round #686(Div.3)

#NameInput/outputLimit
ASpecial Permutationstandard input/output1 s, 256 MB
BUnique Bid Auctionstandard input/output1 s, 256 MB
Cequence Transformationstandard input/output1 s, 256 MB
DNumber into Sequencestandard input/output3 s, 256 MB
Eumber of Simple Pathstandard input/output2s, 256 MB
FArray Partitionstandard input/output2 s, 256 MB

A: “Special Permutation”

問題:
非負整数 n が与えられる。
[1~n]からなる長さnの順列P(p1,p2,….,pn-2,pn-1)で、pi≠i を満たす順列を出力せよ。

解法:
pi = {(i+1) mod i} + 1
にすると条件を満たす。
計算量O(n)

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

int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    for(int i = 0; i < n; ++i){
      if(i!=n-1) cout << (i+1)%n+1 << " ";
      else cout << (i+1)%n+1 << endl;
    }
  }
  return 0;
}

B: “Unique Bid Auction”

問題:
長さnの配列aが与えられる。aの要素で、重複がないもので最小のものを求めよ。
存在しない場合は、”-1″を出力せよ。

解法:
配列を見て行って、重複のないような要素で最小のものを出力する。
存在しない場合-1にする。
計算量O(n)

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

int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    vector<vector<int>> a(n);
    for(int i = 0; i < n; ++i){
      int t;
      cin >> t;
      a[t-1].push_back(i+1);
    }
    int ans = -1;
    for(int i = 0; i < n; ++i){
      if(a[i].size() == 1){
        ans = a[i][0];
        break;
      }
    }
    cout << ans << endl;
  }
  return 0;
}

C: “Sequence Transformation”

問題:
長さnの配列aが与えられる。
aのすべての要素を、x(∈a)にしたい。
aの閉区間[l,r](ai≠x if l≤i≤r)を、削除する操作ができる。
最小の操作回数を求めよ。

解法:
各要素が、どの位置に存在するのかをmapでもっておく。
それぞれの要素で、要素間の距離が隣り合っていない場合、1回の削除操作が必要になる。
この操作の最小回数を追えばいい。
計算量O(n)

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

const int INF = 1e9;
int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    vector<vector<int>> a(n);
    vector<int> cnt(n, INF);
    for(int i = 0; i < n; ++i){
      int t;
      cin >> t;
      a[t-1].push_back(i);
      if(a[t-1].size() == 1) cnt[t-1] = (i==0)?0:1;
      else{
        int prev = a[t-1][a[t-1].size()-2];
        if(i-prev==1) continue;
        else ++cnt[t-1];
      }
    }
    for(int i = 0; i < n; ++i){
      if(a[i].size() >= 1){
        cnt[i] = (a[i][a[i].size()-1]!=n-1)?cnt[i]+1:cnt[i]; 
      }
    }
    int ans = INF;
    for(int i = 0; i < n; ++i){
      ans = min(ans,cnt[i]);
    }
    cout << ans << endl;
  }
  return 0;
}

D: “Number into Sequence”

問題:
1より大きい整数nが与えられる。
以下の条件を満たすような配列aで、最大の長さの配列を求めよ。
・ai > 1
・ ∏ ai = n
・ai+1は、aj(1<=j<=i-1)で割り切れる

解法:
aの長さを最大にするときは、
nを素因数分解したときに最も多く含まれている素因数因子pを並べていったもの-1+あまり*pにするとき。
pをそのような素因数因子の中で最小のものとして、考えると背理法で証明できる。
O(log(n)^2)

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;

int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    ll n;
    cin >> n;
    int ans = 0, rec = INF;
    for(ll i = 2; i <= sqrt(n); ++i){
      ll nn = n;
      int cnt = 0;
      while(nn){
        if(nn%i != 0) break;
        nn /= i;
        ++cnt;
      }
      if(ans < cnt) rec = i, ans = cnt;
    }
    if(rec == INF){
      cout << "1\n";
      cout << n << endl;
    }else{
      cout << ans << "\n";
      for(int i = 0; i < ans-1; ++i){
        cout << rec << " ";
        n /= rec;
      }
      cout << n << endl;
    }
  }
  return 0;
}

E: “Number of Simple Paths”

問題:
G(V,E) = (n,n)の無向グラフが与えられる。
Gは連結である。
Gのパスの総数を求めよ。

解法:
n頂点に、n辺の連結グラフGを考えると、閉路を持つサブグラフ+木の構造になっていることがわかる。
各辺にかんして、ある頂点を含むような辺を重複なくカウントすると答えになる。
閉路を持つサブグラフの頂点集合に、木がくっつている
ような頂点vにかんして、その頂点vに隣接する頂点をqueueでもっておいて一回ずづ探索する。一回探索に使用した辺は削除する。
木の箇所は、(木の頂点集合の大きさ)(木の頂点集合の大きさ-1)/2で木のパスの総数が求まる。 閉路を持つサブグラフと、v、vと隣接する頂点を結ぶようなパスは、(閉路を持つサブグラフの頂点の大きさ)(木の頂点集合の大きさ)で求まる。
計算量O(n)

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

int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    int n;
    cin >> n;
    vector<set<ll>> g(n);
    vector<ll> rec(n,1);
    queue<int> q;
    for(int i = 0; i < n; ++i){
      ll x, y;
      cin >> x >> y;
      x--,y--;
      g[x].insert(y);
      g[y].insert(x);
    }
    for(int i = 0; i < n; ++i){
      if(g[i].size() == 1){
        q.push(i);
      }
    }
    while(!q.empty()){
      int v = q.front();
      q.pop();
      ll to = *g[v].begin();
      rec[to] += rec[v];
      rec[v] = 0;
      g[v].clear();
      g[to].erase(v);
      if(g[to].size() == 1) q.push(to);
    }
    ll ans = 0;
    for(int i = 0; i < n; ++i){
      ans += rec[i] * (rec[i] - 1)/2 + rec[i] * (n - rec[i]);
    }
    cout << ans << endl;
  }
  return 0;
}

F: “Array Partition”

問題:
長さnの配列aが与えられる。
配列aを左から、a1,a2,a3の部分(size(ai)>0)に分割したときに、max(a1) = min(a2) = max(a3)を満たすように配列aを分割できるか?
(max(a1)は、a1の最大の要素、min(a2)は、a2の最小の要素)
できる場合は、分割位置を出力せよ。
できない場合は、Noを出力せよ。

解法:
a2の境界値の左端を順にみて行って、a1の最大値と、a3の最大値が等しくなるようにする。
まず、aを左端から見ていった時の、a[0,i]の最大値(d1)と、aを右端から見ていった時の、a[i,n-1]の最大値(d2)を計算しておく。
次に、aを左端から見ていったとき広義単調増加列となっている部分のa[0,i]の最大値(a1の最大値)と、(pref)
aを右端から見ていったとき広義単調減少列となっている部分の、a[i,n-1]の最大値(a3の最大値)を計算しておく。(suf)
a2の境界値の左端,lを決めたとき、a1の右端を2分探して、l以下の箇所のprefがa1の最大値以下で、a3の左端を2分探索して、l以上の箇所のsufがa3の最大値以下となっているかを調べる。
これが、満たされいるようなa1右端と、a3の左端が存在するとき条件を満たす。
計算量O(nlog(n))

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

int main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);
  int t;
  cin >> t;
  while(t--){
    int n, flag = false;
    cin >> n;
    vector<int> a(n);
    vector<int> d1(n,0),d2(n,0);
    for(int i = 0; i < n; ++i){
      cin >> a[i];
      if(i==0) d1[i] = a[i];
      else d1[i] = max(a[i], d1[i-1]);
    }
    d2[n-1] = a[n-1];
    for(int i = n-2; i>= 0; --i) d2[i] = max(a[i], d2[i+1]);
    vector<int> pref(n,-1);
    vector<int> suf(n,n);
    stack<int> s1;
    stack<int> s2;
    for(int i = 0; i < n; ++i){
      while(!s1.empty() && a[s1.top()] >= a[i]) s1.pop();
      if(s1.empty()) pref[i] = -1;
      else pref[i] = s1.top();
      s1.push(i);
    }
    for(int i = n-1; i >= 0; --i){
      while(!s2.empty() && a[s2.top()] >= a[i]) s2.pop();
      if(s2.empty()) suf[i] = n;
      else suf[i] = s2.top();
      s2.push(i);
    }

    for(int i = 1; i < n-1; ++i){
      int l = 0, r = i-1, mid;
      int val1 = -2, val2 = n+1;
      while(r >= l){
        mid = (l+r)/2;
        if(d1[mid] == a[i]) val1 = mid, l = mid+1;
        else if(d1[mid] < a[i]) l = mid+1;
        else r = mid-1;
      }
      l = i+1, r = n-1;
      while(r >= l){
        mid = (l+r)/2;
        if(d2[mid] == a[i]) val2 = mid, r = mid-1;
        else if(d2[mid] > a[i]) l = mid+1;
        else r = mid-1;
      }
      if(pref[i] <= val1 && suf[i] >= val2){
        cout << "YES" << endl;
        cout << val1+1 << " " << n - (val1 + (n - val2 + 1) ) << " " << n - val2 << endl;
        flag = true;
        break;
      }
    }
    if(!flag) cout << "NO" << endl;
  }
  return 0;
}

終わりに

Div3は初心者向けにほとんど、アルゴリズムの知識を必要とされないコンテストです!

次回は、
Codeforces Round #687 (Div. 1, based on Technocup 2021 Elimination Round 2)
16:05JST
Sunday, 29 November 2020

是非楽しんでください!

その他のコンテスト

AtCoder: ABC184

Leave a Comment