👋

ABC289 参加記録

2023/02/13に公開約7,700字

A - flip

問題概要

012種類のみからなる文字列sが与えられます。sに含まれるすべての01に、10に置き換えて出力してください。

  • 1\leq |s| \leq 10
  • s01のみからなる文字列

解説

どうやってもいいだろうけど、普通にforとかを回せばいい。

実装例

A1.cpp
#include<iostream>
using namespace std;
int main() {
	string s;
	cin >> s;
	for (int i = 0; i < s.size(); i++) {
		if (s[i] == '0') {
			s[i] = '1';
		}
		else {
			s[i] = '0';
		}
	}
	cout << s << '\n';
}
A2.cpp
#include<iostream>
using namespace std;
int main() {
	string s;
	cin >> s;
	for (auto& c : s) {
		if (c == '0') {
			c = '1';
		}
		else {
			c = '0';
		}
	}
	cout << s << '\n';
}

B - レ

問題概要

1からNまでの整数が小さい順に一列に並んでいます。
整数の間には、M個の「レ」が挟まっています。i個目の「レ」は、整数a_iと整数のa_{i+1}の間にあります。
以下の手順に沿って1からNの数字を、一回ずつすべて読みます。

  • 1からNまでの番号のついて無向グラフGを考える。i個目の辺は頂点a_iと頂点a_i+1を結んでいる。
  • 読まれていない整数が存在する間、以下を繰り返す
    • 読まれていない整数のうち、最小のものをxとする。Gにおいてxの含まれる連結成分Cを選び、Cに含まれる頂点の番号を降順にすべて読む。

整数の読まれた順番を教えてください。

  • 1\leq N\leq 100
  • 1\leq M\leq N-1
  • a_i<a_{i+1}(i<M)
  • 1\leq a_i<N
  • 入力される値はすべて整数

解説

...Bにしてはかなり難しいと思う。Cとかにいそう。
ここでは、楽をしてUnionFindを使うことにする。
以下の手順を繰り返し行うことで、答えが求まる。

  • まだ読んでいない頂点であって、最小のもの(問題文中のx)を探す。
    \rightarrow 全て読んでいたら終了
  • y=N,N-1,\dots,1について、x,yが同じ連結成分に属していればyを出力する。

すでに読んでいるか?を持つフラグを管理し、連結判定にUnionFindを使えば十分高速に処理できる。

実装例

ACL(AtCoderLibrary)を使うといい。

B.cpp
#include<bits/stdc++.h>
#include<atcoder/all>
using namespace std;
using namespace atcoder;
int main() {
    int n, m;
    cin >> n >> m;
    dsu uf(n);
    for (int i = 0; i < m; i++) {
        int a;
        cin >> a;
        a--;
        uf.merge(a, a + 1);
    }

    vector<bool> flag(n);
    while (1) {
        int x = -1;
        for (int i = 0; i < n; i++) {
            if (!flag[i]) {
                x = i;
                break;
            }
        }
        if (x == -1) {
            cout << '\n';
            return 0;
        }
        for (int y = n - 1; y >= 0; y--) {
            if (uf.same(x, y)) {
                cout << y + 1 << ' ';
                flag[y] = true;
            }
        }
    }
}

groupメソッドを使って、区間の左右を持っておけばO(N)まで落とせる。

C - Coverage

問題概要

1以上N以下の整数のみからなるM個の集合S_1,S_2,\dots,S_Mがある。
S_i=\{a_{i,1},a_{i,2},\dots,a_{i,C_i}\}からなります。
M個の集合から1個以上の集合を選ぶ方法は2^M-1通りありますが、そのうち、以下の条件を満たすものは何通りありますか?

  • 1\leq x\leq Nなる任意の整数xについて、選んだ集合の中にxを含むようなものが少なくとも一つ存在する。
  • 1\leq N\leq 10
  • 1\leq M\leq 10
  • 1\leq C_i\leq N
  • j<j'\Rightarrow a_{i,j}<a_{i,j'}
  • 1\leq a_{i,j}\leq N
  • 入力はすべて整数

解説

N,Mが十分小さいので、O(2^N)かけて選び方を全パターン列挙することができる。また、そのパターンが条件を満たすかはO(NM)かければ簡単に判定できる。
全列挙の部分ではビット全探索を使うと楽に実装できる。

実装例

setとかでやってもいいけど...Nが小さいので、bit演算のorを使うと楽。

C.cpp
#include<iostream>
#include<vector>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    vector<int> s(m);
    for (auto& ss : s) {
        int c;
        cin >> c;
        for (int i = 0; i < c; i++) {
            int a;
            cin >> a;
            ss |= 1 << a;   //aビット目を立てる
        }
    }
    int ans = 0;
    for (int msk = 1; msk < (1 << m); msk++) {
        int us = 0;   //選んだ集合らの和集合
        for (int i = 0; i < m; i++) {
            if (msk & (1 << i)) {
                us |= s[i];
            }
        }
        bool is_ok = true;
        for (int i = 1; i <= n; i++) {
            if (!(us & (1 << i))) {
                is_ok = false;
            }
        }
        if (is_ok) {
            ans++;
        }
    }
    cout << ans << '\n';
}
MEMO

bitset使ったりするとNが大きくても良かったりするんですかね...?
選んだ集合をS_{i_1},S_{i_2}\dots,S_{i_k}としたとき、条件は

S_{i_1}\cup S_{i_2}\cup\dots\cup S_{i_k}=\{1,2,\dots,N\}

と言い換えられます。これは、bit表現を使えば結構高速(N/64)になるはず。
2^{10}=1024なので定数倍も考えるとN=10^5ぐらいまで行けそう...?

D - Step Up Robot

問題概要

無限に続く階段があり、一番下から順に0段目,1段目...となっている。
いま、0段目にロボットがいる。このロボットは、一回の動作でA_1,A_2,\dots,A_N段分上ることができます。
また、B_1,B_2,\dots,B_M段目には到達することができません。
ちょうどX段目に到達することができますか?

  • 1\leq N\leq 10
  • 1\leq A_1<A_2<\cdots<A_N\leq10^5
  • 1\leq M\leq 10^5
  • 1\leq B_1<B_2\cdots<B_M<X\leq10^5
  • 入力はすべて整数

解説

こういうのはDPをする

  • \text{dp}[i]=i段目に到達できるか?(\text{true/false})

としよう。この時、

  • 初期値:\text{dp[i]}=\text{false}(i\neq 0),\text{dp}[0]=\text{true}
  • 遷移:\text{dp[i]}=\text{dp}[i-A_1]\text{ or }\text{dp}[i-A_2]\text{ or }\cdots\text{ or }\text{dp}{[i-A_N]}

となる。なお、i=b_jなるjが存在するときは、遷移によらず\text{dp[i]}=\text{false}であること注意してください。
これをi=Xまで計算すればいいので、状態数はO(X),遷移はO(N)で計算できます。
以上より、O(NX)で解けました。これは十分高速です。

実装例

D.cpp
#include<iostream>
#include<vector>
using namespace std;
int main() {
	int n;
	cin >> n;
	vector<int> a(n);
	for (auto& ai : a) {
		cin >> ai;
	}
	int m;
	cin >> m;
	vector<int> b(m);
	for (auto& bi : b) {
		cin >> bi;
	}
	int x;
	cin >> x;
	vector<bool> flag(x + 1, false), dp(x + 1, false);
	for (auto& bi : b) {
		flag[bi] = true;
	}

	dp[0] = true;
	for (int i = 1; i <= x; i++) {
		if (flag[i])continue;
		for (auto& ai : a) {
			if (i - ai >= 0 && dp[i - ai]) {
				dp[i] = true;
			}
		}
	}
	if (dp[x])cout << "Yes\n";
	else cout << "No\n";
}

E - Swap Places

問題概要

N頂点M辺の単純無向グラフGが与えられる。また、各頂点には赤,青の色が塗られている。
現在、高橋君は頂点1,青木君は頂点Nにいる。2人は以下の行動を0回以上繰り返すことができます。

  • 2人が同時に、隣接している頂点に移動する。ただし、高橋君の行き先と、青木君の行き先は色が異なる必要がある。

高橋君が頂点Nに、青木君が頂点1にいる状態になるまでに必要な行動回数の最小値を求めてください。不可能なときは-1を出力してください。
テストケースがT個与えられるので、それぞれについて求めてください。

  • 1\leq T\leq 1000
  • 1\leq N\leq 2000
  • 1\leq M\leq\min(\frac{N(N-1)}{2},2000)
  • 与えられるグラフGは単純
  • 全てのテストケースに対するNの総和は2000以下
  • 全てのテストケースに対するMの総和は2000以下
  • 入力はすべて整数

解説

単にグラフ上を調べてもかなり難しい。なぜならば、遷移が2人の位置によるから。
こういう風に、遷移が単に「頂点だけ」「辺だけ」で決定できないときはBFS・DFSなどが使いにくい。
ということで、2人の位置をまとめて「頂点」と見たグラフを作ってみよう。
すなわち、以下のようなグラフG'を作る。

  • 各頂点は「Gの頂点」のペア(v,u)(1\leq v,u\leq N)で表される。
  • 頂点の組p_1=(v_1,u_1),p_2=(v_2,u_2)について、以下の条件を満たすその時に限りp_1\to p_2に辺を張る。
    • Gにおいて、v_1,v_2を結ぶ辺,u_1,u_2を結ぶ辺がいずれも存在する。
    • Gにおいて、u_2に塗られた色とv_2に塗られた色がことなる。

このG'の各頂点(v,u)Gにおいて、

高橋君が頂点vにいて、青木君が頂点uにいる

という状態に対応する。したがって、このG'において(1,N)\to(N,1)の最短経路長を求めればいい。
これはBFSなりDFSなりを使えばO(N^2+M^2)で解ける(厳密な解析は公式解説参照)

実装例

E.cpp
#include<iostream>
#include<queue>
#include<vector>
#include<tuple>
using namespace std;
using graph = vector<vector<int>>;
void solve() {
	int n, m;
	cin >> n >> m;
	vector<int> c(n);
	for (auto& cc : c) {
		cin >> cc;
	}
	graph g(n);
	for (int i = 0; i < m; i++) {
		int u, v;
		cin >> u >> v;
		u--, v--;
		g[v].push_back(u);
		g[u].push_back(v);
	}

	queue<pair<int, int>> que;
	vector<vector<int>> dist(n, vector<int>(n, -1));
	que.emplace(0, n - 1);
	dist[0][n - 1] = 0;
	while (!que.empty()) {
		int u, v;
		tie(u, v) = que.front();
		que.pop();
		for (auto nu : g[u])for (auto nv : g[v]) {
			if (c[nu] == c[nv])continue;
			if (dist[nu][nv] != -1)continue;

			dist[nu][nv] = dist[u][v] + 1;
			que.emplace(nu, nv);
		}
	}
	cout << dist[n - 1][0] << '\n';
}
int main() {
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
}

感想

Bが難しすぎる気がする...

Discussion

ログインするとコメントできます