東京海上日動プログラミングコンテスト2023(AtCoder Beginner Contest 304)


A. First PlayerPermalink

A. First Player

가장 age가 작은 사람으로 rotate 시켜서 순서대로 출력해주면 된다.

B. SubscribersPermalink

B. Subscribers

N<103N < 10^3 이라면 그냥 출력하고 아니면 앞에 세 글자만 보고 뒤에는 00을 붙여서 출력한다.

C. Virus (365)Permalink

C. Virus

O(n2)O(n^2)로 도달되지 않는 것을 가린다.

D. A Piece of Cake (1015)Permalink

D. A Piece of Cake

(pi,qi)(p_i, q_i)A,BA, B 들의 구간에 어디에 들어갈지 이분 탐색으로 찾은 다음에 모든 구간을 데이터가 있는 곳만 O(nlogn)O(n \log n) 으로 탐색해줄 수 있다.

귀찮아서 세그먼트 트리로 풀었다.

E. Good Graph (971)Permalink

E. Good Graph

처음에 간선들을 DSU로 이어서 xi,yix_i, y_i가 같은 컴포넌트에 있는게 있다면 불가능하다.

그렇지 않다면 pi,qip_i, q_i 가 쿼리로 들어왔을 때 pi,qip_i, q_i 의 컴포넌트끼리 이어지게 되는 것인데, 그럼 두 컴포넌트 사이에 (xi,yi)(x_i, y_i) 쌍이 있는지 set 같은걸로 확인한다.

컴포넌트를 나타내는 노드 번호는 DSU에서 각 트리의 루트 번호로 다루면 쉽다.

struct DSU {
   vector<int> p;
   DSU(int n) : p(n, -1) {}
   int gp(int n) {
      if (p[n] < 0) return n;
      return p[n] = gp(p[n]);
   }
   void merge(int a, int b, int to_b = 0) {
      a = gp(a), b = gp(b);
      if (a == b) return;
      if (!to_b && size(a) > size(b)) swap(a, b);
      p[b] += p[a];
      p[a] = b;
   }
   bool is_merged(int a, int b) { return gp(a) == gp(b); }
   int size(int n) { return -p[gp(n)]; }
};
void solve() {
   int n, m, k, q;
   cin >> n >> m;
   DSU dsu(n);
   vvi edges(n);
   for (int i = 0, u, v; i < m; i++) {
      cin >> u >> v, u--, v--, edges[u].pb(v), edges[v].pb(u);
      dsu.merge(u, v);
   }
   cin >> k;
   vector<pi> xy(k);
   for (auto &[x, y]: xy) cin >> x >> y, x--, y--;
   cin >> q;
   // p, q 를 이어도 모든 xy에 대해 여전히 떨어져있을 수 있는가?
   // 처음에 xy 중 붙어있는게있으면 불가능하다.
   // 처음에는 모두 컴포넌트로 분리되어 있을 것이다.
   // p가 속한 컴포넌트와 q가 속한 컴포넌트가 이어졌을 때, 어떤 x[i], y[i]가 존재해 하나는 P에 하나는 Q에 있지 않으면 된다.
   // 컴포넌트별로 번호를 붙이고 (c1, c2) 같은 쌍들을 모두 모은다.
 
   int ok = 1;
   for (int i = 0; i < k; i++) {
      if (dsu.is_merged(xy[i].fi, xy[i].se)) ok = 0;
   }
 
   vector<set<int>> have(n);
   for (auto &[x, y]: xy) {
      x = dsu.gp(x);
      y = dsu.gp(y);
      have[x].insert(y);
      have[y].insert(x);
   }
 
   while (q--) {
      int p, q;
      cin >> p >> q, p--, q--;
      if (!ok) {
         cout << "No\n";
         continue;
      }
      if (dsu.is_merged(p, q)) {
         cout << "Yes\n";
         continue;
      }
      p = dsu.gp(p);
      q = dsu.gp(q);
      if (hass(have[p], q)) cout << "No\n";
      else cout << "Yes\n";
   }
}
 

F. Shift Table (1619)Permalink

F. Shift Table

길이 NN의 진약수 MM 들을 채워넣으면 항상 MM길이의 주기를 이룬다.

NN의 약수는 그리 많지 않으므로 O(NN의 약수 개수)O(N \cdot N\text{의 약수 개수}) 의 시간복잡도로 풀 수 있다는 감을 잡아야한다.

일단 어떤 MM이 주어졌을 때 가능한 것의 개수를 셀 수 있다.

SS에서 MM 개의 위치 중 꼭 출근을 해야하는 날의 개수를 센다. 이것을 PP 라 하자.

그렇지 않아도 상관없는 MPM-P 개의 날이 있으므로 경우의 수는 2MP2^{M-P} 이다.

그런데 이걸 모두 더해주면 중복된 정답이 생긴다.

그럼 항상 MM 길이의 주기가 가장 최소 주기가 되는 것의 개수만 세야한다.

왜냐면 M=8M=8에서 앞에 네개와 뒤에 네개가 같아 주기가 44 라면, 그건 M=4M=4 를 볼 때도 처리가 될 것이기 때문이다.

이 때, 최소 주기만 구하는 방법은 DP를 쓰는 것이다.

위의 예시처럼 M=8M=8 라면 최소주기의 개수가 1,2,41,2,4 인 것을 모두 2MP2^{M-P} 에서 빼줘야 오롯이 MM이 최소주기인 것의 개수가 나온다.

즉, TMT_M을 길이 MM이 최소주기인 것의 개수라고 하면

TM=2MPiMTi\displaystyle T_M= 2^{M-P} \sum_{i \mid M} T_i 가 된다.

MM의 약수 또한 많지 않으므로 그냥 처리해주면 된다.

bitset<MAX + 1> tmp;
int two[MAX + 1];
void solve() {
   sieve();
   two[0] = 1;
   for (int i = 1; i <= MAX; i++) two[i] = md(two[i - 1] * 2);
   int N;
   string S;
   cin >> N >> S;
   vi idx;
 
   for (int i = 0; i < N; i++)
      if (S[i] == '.') idx.pb(i);
   ll ans = 0;
 
   vi dp(N + 1);
   for (int M = 1; M < N; M++)
      if (N % M == 0) {
         tmp.reset();
         for (int i: idx) tmp[i % M] = 1;
         ll t = two[M - tmp.count()];
         dp[M] = t;
         for (int d = 1; d < M; d++) {
            if (M % d == 0) {
               dp[M] = md(dp[M] - dp[d]);
            }
         }
         ans = md(ans + dp[M]);
      }
   cout << ans;
}

G. Max of Medians (3177)Permalink

G. Max of Medians

Ex. Constrained Topological Sort (2642)Permalink

Ex. Constrained Topological Sort

Tags:

Categories:

Updated:

Comments