Fri, Apr 25, 2025 A. Please Sign https://atcoder.jp/contests/arc169/tasks/arc169_a
解説 AC
こちらの動画を見て理解した
https://youtu.be/A79qGX7OFMM?si=PPJI_IB_vRbaFFaZ
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N;
cin >> N;
vll A(N);
rep(i, N) cin >> A[i];
vint depth(N);
vll B(N);
B[0 ] = A[0 ];
rep2(i, 1 , N) {
int p;
cin >> p;
p-- ;
depth[i] = depth[p] + 1 ;
B[depth[i]] += A[i];
}
string ans = "0" ;
for (int i = N - 1 ; i >= 0 ; i-- ) {
if (B[i] == 0 )
continue ;
if (B[i] < 0 )
ans = "-" ;
if (B[i] > 0 )
ans = "+" ;
break ;
}
cout << ans << endl;
}
B. Subsegments with Small Sums https://atcoder.jp/contests/arc169/tasks/arc169_b
Sun, Apr 20, 2025 https://atcoder.jp/contests/abc402
C - Dislike Foods 食材 $i$ を使用している料理の番号、料理 $j$ が使用している苦手な食材の数をそれぞれ保存しておく。
食材 $B_i$ を克服したとき、料理 $j$ が食材 $B_i$ を使用していたら、料理 $j$ の苦手な食材の数を1減らす。苦手な食材の数が 0 になったら食べられる料理の個数を1増やす。
これを繰り返す
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N, M;
cin >> N >> M;
vvll A(M);
rep(i, M) {
int K;
cin >> K;
rep(j, K) {
ll a;
cin >> a;
a-- ;
A[i].push_back(a);
}
}
vll B(N);
rep(i, N) {
cin >> B[i];
B[i]-- ;
}
// shokuzai[i]: 食材 i を使っている料理の番号
vvll shokuzai(N);
rep(i, M) {
for (ll a : A[i]) {
shokuzai[a].push_back(i);
}
}
// nums[i]: 料理 i に含まれる苦手な食材の数
vll nums(M, 0 );
rep(i, M) nums[i] = A[i].size();
ll ans = 0 ;
for (ll b : B) {
for (ll i : shokuzai[b]) {
nums[i]-- ;
if (nums[i] == 0 )
ans++ ;
}
cout << ans << endl;
}
}
D - Line Crossing AC したが、実験して多分これで通るんじゃないかなぁくらいののりで提出して AC した。
Wed, Apr 16, 2025 https://atcoder.jp/contests/past17-open
H - 履修登録 解説 AC.
dp[i][j]
を $i$ 限目までの科目をこなして $j$ 単位取得するのに必要な最小の労力とする。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll n, m;
cin >> n >> m;
vll a(n), b(n), c(n);
rep(i, n) cin >> a[i] >> b[i] >> c[i];
vector< vector< pair< ll, ll>>> lec(5001 );
rep(i, n) {
lec[b[i]].push_back({a[i], c[i]});
}
vvll dp(5001 , vll(m + 1 , INF));
dp[0 ][0 ] = 0 ;
rep2(i, 1 , 5001 ) {
rep(j, m + 1 ) {
// i 限目の科目を取らない場合
chmin(dp[i][j], dp[i - 1 ][j]);
// i 限目の科目を取る場合
rep(k, lec[i].size()) {
chmin(dp[i][j], dp[i - 1 ][max(0ll , j - lec[i][k].second)] + lec[i][k].first);
}
}
}
ll ans = dp[5000 ][m];
if (ans == INF)
ans = - 1 ;
cout << ans << endl;
}
K - 正しいチェックディジット https://atcoder.jp/contests/past17-open/tasks/past17_k
Wed, Apr 16, 2025 https://atcoder.jp/contests/past18-open
G - 二回の交換 $A = B$ のとき、$A_1, A_2$ を交換する操作を2回行えば良い。
$A \neq B$ のときを考える。
交換操作を行うする左端の index として $i < j$ なる $i,j$ を選ぶ。
このとき $(A_i, A_{i+1})$ の交換と $(A_j, A_{j+1})$ の交換操作は一般に非可換である。
一方で今回の問題では交換回数が2回なので次の2通りの置換のみ考えれば十分である。
\begin{eqnarray*}
\begin{pmatrix}
1 & 2 & 3 \\
3 & 1 & 2
\end{pmatrix}
\begin{pmatrix}
1 & 2 & 3 \\
2 & 3 & 1
\end{pmatrix}
\end{eqnarray*}
題意を満たすような操作方法は以下の3通りである
(1) $i$ が小さい方から処理していき $A_i \neq B_i$ なる部分があれば $(A_i, A_{i+1})$ を交換する操作を繰り返し操作回数が2回かつ、最終的に $A = B$ となる (2) $i$ が大きい方から処理しておき $A_i \neq B_i$ なる部分があれば $(A_{i-1}, A_{i})$ を交換する操作を繰り返し操作回数が2回かつ、最終的に $A = B$ となる (3) 一度の交換で $A = B$ となり、かつ、操作前または操作後の数列において同じ数字が連続している部分が存在している (2) に関しては数列を反転させて (1) と同じ操作を行えば良い。
Sat, Apr 12, 2025 https://atcoder.jp/contests/abc401
D - Logical Filling https://atcoder.jp/contests/abc401/tasks/abc401_d
解説 AC
vector< pair< char , int >> runLengthEncode(const string& input) {
vector< pair< char , int >> encoded;
int size = input.size();
for (int i = 0 ; i < size; ++ i) {
int count = 1 ;
while (i + 1 < size && input[i] == input[i + 1 ]) {
++ i;
++ count;
}
encoded.emplace_back(input[i], count);
}
return encoded;
}
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll N, K;
string S;
cin >> N >> K >> S;
rep(i, N) {
if (i + 1 < N && S[i] == '?' && S[i + 1 ] == 'o' ) {
S[i] = '.' ;
}
if (i + 1 < N && S[i] == 'o' && S[i + 1 ] == '?' ) {
S[i + 1 ] = '.' ;
}
}
int no = 0 ;
for (char c : S)
if (c == 'o' )
no++ ;
if (no == K) {
rep(i, N) if (S[i] == '?' ) S[i] = '.' ;
cout << S << endl;
return ;
}
auto ps = runLengthEncode(S);
int m = 0 ;
for (auto [c, num] : ps) {
if (c == 'o' )
m += num;
if (c == '?' )
m += (num + 1 ) / 2 ;
}
if (m > K) {
cout << S << endl;
return ;
}
for (auto [c, num] : ps) {
if (c != '?' ) {
rep(i, num) cout << c;
continue ;
}
if (num % 2 == 0 ) {
rep(i, num) cout << '?' ;
} else {
rep(i, num) {
cout << (i % 2 == 0 ? 'o' : '.' );
}
}
}
cout << endl;
}
E - Reachable Set 解説 AC
Fri, Apr 11, 2025 https://atcoder.jp/contests/abc297
A. Double Click https://atcoder.jp/contests/abc297/tasks/abc297_a
B. chess960 https://atcoder.jp/contests/abc297/tasks/abc297_b
C. PC on the Table https://atcoder.jp/contests/abc297/tasks/abc297_c
D. Count Subtractions https://atcoder.jp/contests/abc297/tasks/abc297_d
E. Kth Takoyaki Set https://atcoder.jp/contests/abc297/tasks/abc297_e
ダイクストラみたいな感じで解く。距離 0 のところから始めてコスト $A_i$ で別のノードに移動。距離最初のノードからまたコスト $A_i$ で移動・・・というのを繰り返す。訪れたノードの数が $K+1$ になったら終了(最初の 0 のノードを数えているので $+1$ している)。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N, K;
cin >> N >> K;
vll A(N);
rep(i, N) cin >> A[i];
set< ll> check, stock;
stock.insert(0 );
while ((int )check.size() != K + 1 ) {
auto it = stock.begin();
ll x = * it;
stock.erase(it);
check.insert(x);
for (ll a : A)
stock.insert(x + a);
}
cout << * check.rbegin() << endl;
}
F. Minimum Bounding Box 2 https://atcoder.jp/contests/abc297/tasks/abc297_f
Tue, Apr 8, 2025 https://atcoder.jp/contests/abc324
A. Same https://atcoder.jp/contests/abc324/tasks/abc324_a
B. 3-smooth Numbers https://atcoder.jp/contests/abc324/tasks/abc324_b
C. Error Correction https://atcoder.jp/contests/abc324/tasks/abc324_c
D. Square Permutation https://atcoder.jp/contests/abc324/tasks/abc324_d
E. Joint Two Strings https://atcoder.jp/contests/abc324/tasks/abc324_e
$S_i$ の部分列と $T$ の prefix との共通部分の長さが $k$ となる $i$ の個数を $h_k$ とする。
同様に $S_i$ の部分列と $T$ の suffix との共通部分の長さが $k$ となる $i$ の個数を $t_k$ とする。
$T$ の prefix との共通部分の長さが $k$ となる文字には、$T$ の suffix との共通部分の長さが $N-k$ 以上となる文字を連結させれば $T$ を部分列として含むことができる。
よって求める個数は
\begin{align*}
\sum_{k=0}^{N} \left\{ h_k \cdot \left( \sum_{j = N-k}^N t_{j} \right) \right\}
\end{align*}
Tue, Apr 8, 2025 https://atcoder.jp/contests/abc305
A. Water Station https://atcoder.jp/contests/abc305/tasks/abc305_a
B. ABCDEFG https://atcoder.jp/contests/abc305/tasks/abc305_b
C. Snuke the Cookie Picker https://atcoder.jp/contests/abc305/tasks/abc305_c
D. Sleep Log https://atcoder.jp/contests/abc305/tasks/abc305_d
E. Art Gallery on Graph https://atcoder.jp/contests/abc305/tasks/abc305_e
頂点 $i$ に体力 $h_i$ の警備員がいるとき、隣接する頂点に警備員が移動して体力 $h_i - 1$ の警備員がいるとみなしても良い。
体力が大きい警備員がいる頂点から順に隣接する頂点に移動させて、各頂点に体力が 0 以上の警備員がいる頂点が列挙すべき頂点
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N, M, K;
cin >> N >> M >> K;
vvint graph(N);
rep(i, M) {
int u, v;
cin >> u >> v;
u-- , v-- ;
graph[u].push_back(v);
graph[v].push_back(u);
}
vll powers(N, - 1 );
rep(i, K) {
int p, h;
cin >> p >> h;
p-- ;
powers[p] = h;
}
// power, node id
using P = pair< ll, int > ;
priority_queue< P> pq;
rep(i, N) {
if (powers[i] > 0 )
pq.push({powers[i], i});
}
while (pq.size()) {
auto [power, id] = pq.top();
pq.pop();
if (powers[id] > power)
continue ;
for (int nx : graph[id]) {
if (powers[nx] >= power - 1 )
continue ;
powers[nx] = power - 1 ;
pq.push({powers[nx], nx});
}
}
vint ans;
rep(i, N) {
if (powers[i] >= 0 )
ans.push_back(i + 1 );
}
cout << ans.size() << endl;
print(ans);
}
F. Dungeon Explore https://atcoder.jp/contests/abc305/tasks/abc305_f
Mon, Apr 7, 2025 https://atcoder.jp/contests/past15-open
E - 合計得点 bit 全探索で良いが $N$ 個から $K$ 個選ぶ方法でも解ける
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, k;
cin >> n >> k;
vll a(n);
rep(i, n) cin >> a[i];
vint v;
auto dfs = [& ](auto dfs, int used) -> void {
if (__builtin_popcount(used) == k) {
v.push_back(used);
return ;
}
int id = - 1 ;
rep(i, n) {
if ((used >> i) & 1 )
id = i;
}
rep2(i, id + 1 , n) {
dfs(dfs, used | (1 << i));
}
};
dfs(dfs, 0 );
ll ans = 0 ;
for (int used : v) {
rep(i, n) {
if ((used >> i) & 1 )
ans += a[i];
}
}
cout << ans << endl;
}
F - 番号付け void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N;
cin >> N;
vint A(N);
rep(i, N) cin >> A[i];
map< int , vint> mp;
rep(i, N) {
mp[A[i]].push_back(i);
}
int cnt = 1 ;
vint ans(N);
for (auto it = mp.begin(); it != mp.end(); it++ ) {
auto [_, ids] = * it;
for (int id : ids) {
ans[id] = cnt++ ;
}
}
print(ans);
}
G - N-SAT void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N, M;
cin >> N >> M;
vvint as(M), bs(M);
rep(i, M) {
int k;
cin >> k;
rep(j, k) {
int a, b;
cin >> a >> b;
a-- ;
as[i].push_back(a);
bs[i].push_back(b);
}
}
rep(t, 1 << N) {
vint xs;
rep(i, N) {
xs.push_back((t >> i) & 1 );
}
int cnt = 0 ;
rep(i, M) {
int ok = 0 ;
int ki = as[i].size();
rep(j, ki) {
if (xs[as[i][j]] == bs[i][j])
ok = 1 ;
}
if (ok)
cnt++ ;
}
if (cnt == M) {
yesno(true);
return ;
}
}
yesno(false);
}
H - 和で表現 解説 AC。愚直解で AC 取れてしまったが解説読んだら二分探索だった。
Sat, Apr 5, 2025 https://atcoder.jp/contests/abc400
C. 2^a b^2 https://atcoder.jp/contests/abc400/tasks/abc400_c
AC したがなぜ AC したのかよくわからず AC した。sqrt
が誤差を生むので出てきた数の周辺で検算をするようにして AC したが知識がないとsqrt を使ったときに落とせるテストケースもかけないのでかなり難しい問題に感じた。
$2^a$ を動かして取りうる $b^2$ の個数を求める方法だと $X = 16 = 2^2 \times 2^2 = 2^4 \times 1^2$ のように同じ数に対して重複してカウントしてしまうことがある。
2の冪はすべて $2^a$ の方に押し込んで、$b$ は奇数という制約をかけると重複しない。
\begin{align*}
2^k \times b^2 \leq N
&\Rightarrow b^2 \leq N / 2^k \\
&\Rightarrow b \leq \sqrt{N/2^k} \\
&\Rightarrow b \leq \lfloor \sqrt{N/2^k} \rfloor ~~~(\because b \text{ is an integer})
\end{align*}
であり $[1, \lfloor \sqrt{N/2^k} \rfloor]$ に含まれる奇数の個数は $\displaystyle \left\lfloor \frac{\lfloor \sqrt{N/2^k} \rfloor + 1}{2} \right\rfloor$ である。
Sun, Mar 30, 2025 https://atcoder.jp/contests/past19-open
C - 良さそうな数 / Goodish or Not DP で解いたが、解説読んで普通に桁を変えるのを全探索すればいいだけだった
DP で解く場合は dp[i][j]
を左から $i$ 桁目を $j$ にしたときのそれまで変更回数の最小値とする。
// DP 解放
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
string S;
cin >> S;
int n = S.size();
if (n == 1 ) {
yesno(true);
return ;
}
vint v(n);
rep(i, n) {
v[i] = S[i] - '0' ;
}
vvint dp(n + 1 , vint(10 , INF));
rep2(i, 1 , 10 ) {
if (v[0 ] != i)
dp[0 ][i] = 1 ;
else
dp[0 ][i] = 0 ;
}
rep2(i, 1 , n) {
rep(j, 10 ) {
int change = v[i] != j;
if (j - 1 >= 0 ) {
chmin(dp[i][j], dp[i - 1 ][j - 1 ] + change);
}
if (j + 1 <= 9 ) {
chmin(dp[i][j], dp[i - 1 ][j + 1 ] + change);
}
chmin(dp[i][j], dp[i - 1 ][j] + change);
}
}
int ok = 0 ;
rep(i, 10 ) {
if (dp[n - 1 ][i] <= 1 )
ok = 1 ;
}
yesno(ok);
}
// 全探索解法
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
string S;
cin >> S;
int n = S.size();
vint v(n);
rep(i, n) {
v[i] = S[i] - '0' ;
}
auto check = [](vint& v) -> bool {
bool ok = true;
int n = v.size();
if (n == 1 )
return true;
if (v[0 ] == 0 )
return false;
rep(i, n - 1 ) {
if (abs(v[i] - v[i + 1 ]) > 1 )
ok = false;
}
return ok;
};
rep(i, n) {
vint t = v;
rep(j, 10 ) {
t[i] = j;
if (check(t)) {
yesno(true);
return ;
}
}
}
yesno(false);
}
K - 隣接禁止 木 DP の問題
Sun, Mar 30, 2025 https://atcoder.jp/contests/abc064
A. i i’s https://atcoder.jp/contests/agc064/tasks/agc064_a
解説読んでもどうやったらその思考になるのかわからず。
結局諦めた
B. Red and Blue Spanning Tree https://atcoder.jp/contests/agc064/tasks/agc064_b
C. Erase and Divide Game https://atcoder.jp/contests/agc064/tasks/agc064_c
D. Red and Blue Chips https://atcoder.jp/contests/agc064/tasks/agc064_d
E. Cross Sum Construction https://atcoder.jp/contests/agc064/tasks/agc064_e
F. No Permutations https://atcoder.jp/contests/agc064/tasks/agc064_f
Sun, Mar 30, 2025 https://atcoder.jp/contests/abc399
A. Hamming Distance https://atcoder.jp/contests/abc399/tasks/abc399_a
B. Ranking with Ties https://atcoder.jp/contests/abc399/tasks/abc399_b
C. Make it Forest https://atcoder.jp/contests/abc399/tasks/abc399_c
D. Switch Seats https://atcoder.jp/contests/abc399/tasks/abc399_d
$A_i = A_j = a, (i < j)$ とする。
(1) $i+1=j$ のとき
$a$ が隣接しているので求める整数対の対象にならない
(2) それ以外のとき
求める整数対の候補となるのは
$A_{i-1} = A_{j-1}$ (baba 型) $A_{i-1} = A_{j+1}$ (baab 型) $A_{i+1} = A_{j-1}$ (abba 型) $A_{i+1} = A_{j+1}$ (abab 型) の4つのケースである。
(2.1) $A_{i+1} = A_{j-1}$ のとき
$i+2 = j$ のとき、同じ数字を参照しているので対象外. ($a b a$ で $b$ を参照しているケース) $i+3 = j$ のとき、同じ数字が隣接しているので対象外. ($a b b a$ で $b$ を参照しているケース) それ以外のとき、$(\min(a,b), \max(a,b))$ を答えに追加する (2.2) それ以外の3ケースのとき
Sat, Mar 29, 2025 https://atcoder.jp/contests/past202004-open
K - 括弧 https://atcoder.jp/contests/past202004-open/tasks/past202004_k
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int N;
string S;
cin >> N >> S;
S = "_" + S;
vll C(N + 1 ), D(N + 1 );
rep(i, N) cin >> C[i + 1 ];
rep(i, N) cin >> D[i + 1 ];
// dp[i][j]: i 文字目まで見たときの、対応が取れていない左カッコの数が j のときの最小コスト
vvll dp(N + 1 , vll(N + 1 , INF));
dp[0 ][0 ] = 0 ;
rep2(i, 1 , N + 1 ) {
rep(j, N) {
if (S[i] == '(' ) {
chmin(dp[i][j + 1 ], dp[i - 1 ][j]);
// 反転
if (j - 1 >= 0 )
chmin(dp[i][j - 1 ], dp[i - 1 ][j] + C[i]);
} else {
if (j - 1 >= 0 )
chmin(dp[i][j - 1 ], dp[i - 1 ][j]);
// 反転
chmin(dp[i][j + 1 ], dp[i - 1 ][j] + C[i]);
}
// i 番目の要素を削除
chmin(dp[i][j], dp[i - 1 ][j] + D[i]);
}
}
cout << dp[N][0 ] << endl;
}
Fri, Mar 28, 2025 A. Repdigit Number https://atcoder.jp/contests/arc149/tasks/arc149_a
B. Two LIS Sum https://atcoder.jp/contests/arc149/tasks/arc149_b
実験していて $A_i$ で sort して $B_i$ の LIS を求めるだけで良さそうだなと思って実装したら AC した。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
vector< pair< int , int >> ps(n);
rep(i, n) {
int x;
cin >> x;
ps[i].first = x;
}
rep(i, n) {
int x;
cin >> x;
ps[i].second = x;
}
sort(all(ps));
ll ans = n;
vint v(n + 1 , INF);
rep(i, n) {
auto it = upper_bound(all(v), ps[i].second);
* it = ps[i].second;
}
ans += lower_bound(all(v), INF) - v.begin();
cout << ans << endl;
}
C. Avoid Prime Sum https://atcoder.jp/contests/arc149/tasks/arc149_c
Fri, Mar 28, 2025 A. Operations on a Stack https://atcoder.jp/contests/arc194/tasks/arc194_a
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
vll a(n + 1 );
rep(i, n) cin >> a[i + 1 ];
// dp[i][j]: i 回目の操作で操作 j を行ったときの S の総和の最大値
// j = 0: 何もしない
// j = 1: a[i] を入れる
vvll dp(n + 1 , vll(2 , - INF));
dp[0 ][1 ] = 0 ;
rep2(i, 1 , n + 1 ) {
chmax(dp[i][1 ], dp[i - 1 ][1 ] + a[i]);
chmax(dp[i][1 ], dp[i - 1 ][0 ] + a[i]);
if (i - 2 >= 0 ) {
// i-1 回目で要素を入れて、i 回目で要素を取り出すと何もしていないのと同じ
chmax(dp[i][0 ], dp[i - 2 ][1 ]);
chmax(dp[i][0 ], dp[i - 2 ][0 ]);
}
}
cout << max(dp[n][0 ], dp[n][1 ]) << '\n' ;
}
B. Minimum Cost Sort https://atcoder.jp/contests/arc194/tasks/arc194_b
Tue, Mar 25, 2025 https://atcoder.jp/contests/abc244
A. Last Letter https://atcoder.jp/contests/abc244/tasks/abc244_a
B. Go Straight and Turn Right https://atcoder.jp/contests/abc244/tasks/abc244_b
C. Yamanote Line Game https://atcoder.jp/contests/abc244/tasks/abc244_c
D. Swap Hats https://atcoder.jp/contests/abc244/tasks/abc244_d
E. King Bombee https://atcoder.jp/contests/abc244/tasks/abc244_e
dp[k][i][j]
: $k$ 回移動後、頂点 $i$ にたどり着くまでに頂点 $X$ を偶数($j=0$)/奇数($j=1$)回訪れる経路の数
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll N, M, K, S, T, X;
cin >> N >> M >> K >> S >> T >> X;
S-- , T-- , X-- ;
vvint graph(N);
rep(i, M) {
int u, v;
cin >> u >> v;
u-- , v-- ;
graph[u].push_back(v);
graph[v].push_back(u);
}
// dp[k][i][j]: k 回移動後、頂点 i にたどり着くまでに頂点 X を偶数(j=0)/奇数(j=1)回訪れる経路の数
vector< vector< vector< mint>>> dp(K + 1 , vector< vector< mint>> (N, vector< mint> (2 )));
dp[0 ][S][0 ] = 1 ;
rep2(i, 1 , K + 1 ) {
rep(j, N) {
for (int nx : graph[j]) {
if (nx == X) {
dp[i][nx][0 ] += dp[i - 1 ][j][1 ];
dp[i][nx][1 ] += dp[i - 1 ][j][0 ];
} else {
dp[i][nx][0 ] += dp[i - 1 ][j][0 ];
dp[i][nx][1 ] += dp[i - 1 ][j][1 ];
}
}
}
}
cout << dp[K][T][0 ].val() << endl;
}
F. Shortest Good Path https://atcoder.jp/contests/abc244/tasks/abc244_f
Mon, Mar 24, 2025 https://atcoder.jp/contests/abc286
A. Range Swap https://atcoder.jp/contests/abc286/tasks/abc286_a
B. Cat https://atcoder.jp/contests/abc286/tasks/abc286_b
C. Rotate and Palindrome https://atcoder.jp/contests/abc286/tasks/abc286_c
D. Money in Hand https://atcoder.jp/contests/abc286/tasks/abc286_d
E. Souvenir https://atcoder.jp/contests/abc286/tasks/abc286_e
ワーシャルフロイドで解く。距離が短い場合は価値も更新. 距離が同じ場合は価値を高い方に更新する。
はじめわーシャルフロイドの添字の実行順があったことを忘れて WA したが、なぜか3回回したら AC した。
まぐれと思ったが3回実行すると正しい答えが返ってくることは証明されているらしい。
https://qiita.com/tmaehara/items/f56be31bbb7a468a04ed
コード中の注意点として距離が大きいときの条件と同じときの条件をまとめて書いてはいけない
OK
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
value[i][j] = value[i][k] + value[k][j] - prices[k];
}
if (dist[i][j] == dist[i][k] + dist[k][j]) {
chmax(value[i][j], value[i][k] + value[k][j] - prices[k]);
}
NG
if (dist[i][j] >= dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
chmax(value[i][j], value[i][k] + value[k][j] - prices[k]);
}
NG 版で書くと距離が大きいときの価値の値を使ってしまうことがあるため WA する。
Sun, Mar 23, 2025 https://atcoder.jp/contests/abc320
A. Leyland Number https://atcoder.jp/contests/abc320/tasks/abc320_a
B. Longest Palindrome https://atcoder.jp/contests/abc320/tasks/abc320_b
C. Slot Strategy 2 (Easy) https://atcoder.jp/contests/abc320/tasks/abc320_c
D. Relative Position https://atcoder.jp/contests/abc320/tasks/abc320_d
E. Somen Nagashi https://atcoder.jp/contests/abc320/tasks/abc320_e
並んでいる人は id が小さい順で常に並んでいる、列から離れた人は帰ってくる時間が小さい順に並んでいると考えればそれぞれを priority queue で管理すれば問題が解けることがわかる
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, m;
cin >> n >> m;
vll ans(n);
priority_queue< int , vint, greater< int >> people;
// time, id
using P = pair< ll, int > ;
priority_queue< P, vector< P> , greater< P>> tpq;
rep(i, n) people.push(i);
rep(i, m) {
ll t, w, s;
cin >> t >> w >> s;
// そうめんが流れてくるまでに列に戻ってくる人を列に戻す
while (tpq.size() && tpq.top().first <= t) {
auto [t, id] = tpq.top();
tpq.pop();
people.push(id);
}
// 列に誰もいなかったら何もしない
if (people.size() == 0 )
continue ;
auto p = people.top();
people.pop();
ans[p] += w;
tpq.push({t + s, p});
}
for (ll x : ans)
cout << x << '\n' ;
}
F. Fuel Round Trip https://atcoder.jp/contests/abc320/tasks/abc320_f
Sun, Mar 23, 2025 https://atcoder.jp/contests/abc398
A. Doors in the Center https://atcoder.jp/contests/abc398/tasks/abc398_a
B. Full House 3 https://atcoder.jp/contests/abc398/tasks/abc398_b
C. Uniqueness https://atcoder.jp/contests/abc398/tasks/abc398_c
D. Bonfire https://atcoder.jp/contests/abc398/tasks/abc398_d
煙の位置は変えずに焚き火と高橋くんの位置を移動させる。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll n, row, col;
cin >> n >> row >> col;
string s;
cin >> s;
using P = pair< ll, ll> ;
set< P> memo;
memo.insert({0 , 0 });
int si = 0 , sj = 0 ;
for (char c : s) {
if (c == 'N' ) {
si++ ;
row++ ;
}
if (c == 'W' ) {
sj++ ;
col++ ;
}
if (c == 'S' ) {
si-- ;
row-- ;
}
if (c == 'E' ) {
sj-- ;
col-- ;
}
memo.insert({si, sj});
if (memo.count({row, col})) {
cout << 1 ;
} else {
cout << 0 ;
}
}
cout << endl;
}
E. Tree Game https://atcoder.jp/contests/abc398/tasks/abc398_e
Sat, Mar 22, 2025 A. Exchange https://atcoder.jp/contests/arc177/tasks/arc177_a
B. Puzzle of Lamps https://atcoder.jp/contests/arc177/tasks/arc177_b
C. Routing https://atcoder.jp/contests/arc177/tasks/arc177_c
条件1について考える。赤色のマスを紫色に塗ってもスタートとゴールの連結性は変わらないから紫色に変えるべきマスは青色のマスである。
同様に条件2を達成するためには赤色のマスを紫色に変える必要がある。
よって条件1, 2を満たすために必要な紫色に変えるマスは独立して考えることができる。
よって問題の部分問題は次のように言い換えることができる。
各マスは赤か白で塗られており $c_{i,j} = 'R'$ のとき赤、$c_{i,j} = 'W'$ のとき白で塗られている。赤マス間は自由に行き来することできる。
スタートとゴールは赤マスである。
スタートとゴールが連結なるためには最低何マス追加で赤マスに変える必要があるか。
これを青マスについても同様に解いて、その和を求めればよい。
白マスへの移動はコスト $+1$、それ以外の移動は コスト $0$ であると考えると、スタートからゴールまでの最短経路を求める問題として解くことができる。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
vector< vector< char >> B(n, vector< char > (n, '_' )), R(n, vector< char > (n, '_' ));
rep(i, n) {
rep(j, n) {
char c;
cin >> c;
if (c == 'R' )
R[i][j] = c;
else
B[i][j] = c;
}
}
vint di = {1 , 0 , - 1 , 0 };
vint dj = {0 , - 1 , 0 , 1 };
auto cal = [& ](vector< vector< char >>& grid, int si, int sj, int ti, int tj) -> ll {
vvll dist(n, vll(n, INF));
dist[si][sj] = 0 ;
using P = tuple< int , int , int > ;
priority_queue< P, vector< P> , greater< P>> que;
que.push({0 , si, sj});
while (que.size()) {
auto [cost, i, j] = que.top();
que.pop();
if (dist[i][j] < cost)
continue ;
rep(d, 4 ) {
int ni = i + di[d], nj = j + dj[d];
if (clamp(ni, 0 , n - 1 ) != ni || clamp(nj, 0 , n - 1 ) != nj)
continue ;
if (grid[ni][nj] == '_' ) {
if (dist[ni][nj] <= dist[i][j] + 1 )
continue ;
dist[ni][nj] = dist[i][j] + 1 ;
} else {
if (dist[ni][nj] <= dist[i][j])
continue ;
dist[ni][nj] = dist[i][j];
}
que.push({dist[ni][nj], ni, nj});
}
}
return dist[ti][tj];
};
ll ans = cal(R, 0 , 0 , n - 1 , n - 1 ) + cal(B, 0 , n - 1 , n - 1 , 0 );
cout << ans << endl;
}
D. Earthquakes https://atcoder.jp/contests/arc177/tasks/arc177_d
Sat, Mar 22, 2025 A. Chmax Rush https://atcoder.jp/contests/arc182/tasks/arc182_a
解説 AC
$i$ 番目の操作は、左操作か、右操作か、どちらでも良いかの3通りがある。
$i$ 番目の操作が左か右操作のいずれかに確定している場合はそれを実行しなければならないので、$2^\text{どちらの操作をしても良い回数}$ が答えとなる
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, q;
cin >> n >> q;
vint P(q), V(q);
rep(i, q) {
cin >> P[i] >> V[i];
P[i]-- ;
}
// kind[i]: i 回目にどの操作を必要があるか
// -1 左操作
// 1 右操作
// 0 どちらでも良い
vint kind(q);
auto change = [& ](int i, int op) -> bool {
if (kind[i] == 0 ) {
kind[i] = op;
return true;
}
if (kind[i] != op) {
// 操作が矛盾
return false;
}
return true;
};
rep(i, q) {
rep2(j, i + 1 , q) {
if (V[i] <= V[j])
continue ;
if (P[i] == P[j]) {
cout << 0 << endl;
return ;
}
bool ok = true;
if (P[i] < P[j]) {
// i 番目の操作で左操作
// j 番目の操作で右操作
// をしなければならない
ok = ok && change(i, - 1 );
ok = ok && change(j, 1 );
}
if (P[i] > P[j]) {
// i 番目の操作で右操作
// j 番目の操作で左操作
// をしなければならない
ok = ok && change(i, 1 );
ok = ok && change(j, - 1 );
}
if (! ok) {
cout << 0 << endl;
return ;
}
}
}
mint two = 2 ;
ll cnt = 0 ;
rep(i, q) cnt += kind[i] == 0 ;
cout << two.pow(cnt).val() << endl;
}
B. |{floor(A_i/2^k)}| https://atcoder.jp/contests/arc182/tasks/arc182_b
Fri, Mar 21, 2025 https://atcoder.jp/contests/abc322
A. First ABC 2 https://atcoder.jp/contests/abc322/tasks/abc322_a
B. Prefix and Suffix https://atcoder.jp/contests/abc322/tasks/abc322_b
C. Festival https://atcoder.jp/contests/abc322/tasks/abc322_c
D. Polyomino https://atcoder.jp/contests/abc322/tasks/abc322_d
E. Product Development https://atcoder.jp/contests/abc322/tasks/abc322_e
解説 AC.
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, k, p;
cin >> n >> k >> p;
vll c(n + 1 );
vvll A(n + 1 , vll(k));
rep(i, n) {
cin >> c[i + 1 ];
rep(j, k) {
cin >> A[i + 1 ][j];
}
}
vector< map< vll, ll>> dp(n + 1 );
dp[0 ][vll(k, 0 )] = 0 ;
rep2(i, 1 , n + 1 ) {
dp[i] = dp[i - 1 ];
for (auto [v, val] : dp[i - 1 ]) {
vll t = v;
rep(j, k) {
t[j] += A[i][j];
if (t[j] > p)
t[j] = p;
}
if (dp[i].count(t)) {
chmin(dp[i][t], val + c[i]);
} else {
dp[i][t] = val + c[i];
}
}
}
vll v(k, p);
if (dp[n].count(v)) {
cout << dp[n][v] << endl;
} else {
cout << - 1 << endl;
}
}
F. Vacation Query https://atcoder.jp/contests/abc322/tasks/abc322_f
Thu, Mar 20, 2025 https://atcoder.jp/contests/abc303
A. Similar String https://atcoder.jp/contests/abc303/tasks/abc303_a
B. Discord https://atcoder.jp/contests/abc303/tasks/abc303_b
C. Dash https://atcoder.jp/contests/abc303/tasks/abc303_c
D. Shift vs. CapsLock https://atcoder.jp/contests/abc303/tasks/abc303_d
E. A Gift From the Stars https://atcoder.jp/contests/abc303/tasks/abc303_e
葉から始めて距離が3離れたノードは別の star graph の葉に相当する。また葉の隣のノードは star graph の中心。
これよりある葉から出発して $3i+1$ 離れたノードは star graph の中心だったものである。
これより中心の頂点番号が取れるのでそのノードにおける次数を昇順に出力すればよい。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
vvint graph(n);
rep(i, n - 1 ) {
int u, v;
cin >> u >> v;
u-- , v-- ;
graph[u].push_back(v);
graph[v].push_back(u);
}
vint sz;
auto dfs = [& ](auto dfs, int now, int p, int label) -> void {
if (label == 1 ) {
sz.push_back(graph[now].size());
}
for (int nx : graph[now]) {
if (nx == p)
continue ;
dfs(dfs, nx, now, (label + 1 ) % 3 );
}
};
rep(i, n) if (graph[i].size() == 1 ) {
dfs(dfs, i, - 1 , 0 );
break ;
}
sort(all(sz));
print(sz);
}
F. Damage over Time https://atcoder.jp/contests/abc303/tasks/abc303_f
Thu, Mar 20, 2025 A. Right String https://atcoder.jp/contests/arc140/tasks/arc140_a
解説 AC。
答えが $N$ の約数のいずれかになることまではわかったが最終的な答えの求め方を間違えた。
$d|N$ となる $d$ について考える。$S$ を下記のように $d$ 個ずつに分割する。
1 2 $\cdots$ d $S_1$ $S_2$ $\cdots$ $S_d$ $S_{d+1}$ $S_{d+2}$ $\cdots$ $S_{2d}$ $\vdots$ $\vdots$ $\ddots$ $\vdots$ $S_{N-d+1}$ $S_{N-d+2}$ $\cdots$ $S_N$
列 $i$ に対して最頻出文字に全ての文字を置き換えるのに必要な操作回数を $c_i$ とすると $\sum_i c_i \leq K$ のとき題意の操作を実行してできる文字種は $d$ になる。よって $f(S) \leq d$.
同様に全ての $N$ の約数について調べ $f(S) \leq d$ を達成できる最小の $d$ が答え。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, k;
string s;
cin >> n >> k >> s;
ll ans = n;
rep2(d, 1 , n) {
if (n % d)
continue ;
int sum = 0 ;
rep(i, d) {
vint cnt(26 , 0 );
for (int j = i; j < n; j += d) {
cnt[s[j] - 'a' ]++ ;
}
sum += * max_element(all(cnt));
}
if (n - sum <= k)
chmin(ans, d);
}
cout << ans << endl;
}
Mon, Mar 17, 2025 https://atcoder.jp/contests/abctypical90
001. Yokan Party(★4) https://atcoder.jp/contests/typical90/tasks/typical90_a
解説 AC
最小値を最大化する問題は二分探索を使うのが定石らしい。
最小値を $m$ としたときの分割数が $K+1$ 以上ならば最小値を $m$ 以上にすることができる。そうでないならば最小値を $m$ にすることはできない。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll N, L, K;
cin >> N >> L >> K;
vll A(N);
rep(i, N) cin >> A[i];
A.insert(A.begin(), 0 );
A.push_back(L);
vll diff;
rep(i, N + 1 ) {
diff.push_back(A[i + 1 ] - A[i]);
}
int m = diff.size();
ll ac = 0 , wa = L + 1 ;
while (abs(ac - wa) > 1 ) {
ll wj = (ac + wa) / 2 ;
int i = 0 ;
ll cnt = 0 ;
ll sum = 0 ;
rep(i, m) {
sum += diff[i];
if (sum >= wj) {
sum = 0 ;
cnt++ ;
}
}
if (cnt >= K + 1 ) {
ac = wj;
} else {
wa = wj;
}
}
cout << ac << endl;
}
002 - Encyclopedia of Parentheses(★3) https://atcoder.jp/contests/typical90/tasks/typical90_b
Sun, Mar 16, 2025 A. Neq Number https://atcoder.jp/contests/arc173/tasks/arc173_a
$x$ 以下の Neq Number の個数を求める関数を書いて二分探索すれば良いという方針は立ったが実装に手間取った。
結局うまく実装できず解説を読んだり解説放送を見たが詳細な実装方法については触れておらず結局自分で考えた。
$f(x)$ を $x$ 以下の Neq Number の個数とする。$f(x) = K$ となる最小の $x$ が求める答えとなる。
$f(x)$ は広義単調増加関数であるから二分探索で答えを求めることができる。
$d$ 桁の Neq Number の個数を考える。左の桁を 0 桁目とすると 0 桁目は 1 から 9 までの 9 通りである。
$i > 0$ 桁目の数は 0~9 の10通りのうち $i-1$ 桁目とは異なる数であるであるから 9 通りである。
よって $d$ 桁の Neq Number の個数は $9^{d}$ 通りである。
$f(x)$ の実装について考える。
$x$ が1桁のとき $f(x) = x$ である。
$x$ が $d$ 桁のとき まず $d$ 桁未満の Neq Number の個数は $\sum_{k=1}^{d-1} 9^{k}$ である。
Sun, Mar 16, 2025 https://atcoder.jp/contests/abc397
A. Thermometer https://atcoder.jp/contests/abc397/tasks/abc397_a
B. Ticket Gate Log https://atcoder.jp/contests/abc397/tasks/abc397_b
C. Variety Split Easy https://atcoder.jp/contests/abc397/tasks/abc397_c
D. Cubes https://atcoder.jp/contests/abc397/tasks/abc397_d
解説 AC
$d = x-y$ とすると
\begin{align*}
x^3 - y^3
&= (d+y)^3 - y^3 \\
&= d^3 + 3d^2y + 3dy^2 + y^3 - y^3 \\
&= d^3 + 3d^2y + 3dy^2 \\
&= N
\end{align*}
となる。ここで $3d^2y + 3dy^2 > 0$ だから $d^3 < N$ である。
さらに
\begin{align*}
&x^3 - y^3 = N \\
&(x-y)(x^2 + xy + y^2) = N \\
&d((d+y)^2 + (d+y)y + y^2) = N \\
&(d^2 + 2dy + y^2) + (dy + y^2) + y^2 = N/d \\
&3y^2 + 3dy + d^2 - N/d = 0
\end{align*}
Sun, Mar 16, 2025 https://atcoder.jp/contests/abc216
E - Amusement Park https://atcoder.jp/contests/abc216/tasks/abc216_e
貪欲解法 明らかに、楽しさが大きいアトラクションから乗るのが最適である。
愚直に priority queue などに数値を入れてシミュレーションをすると時間がかかりすぎて TLE になる。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll n, k;
cin >> n >> k;
vll a(n);
rep(i, n) cin >> a[i];
map< ll, ll> mp;
for (ll x : a)
mp[x]++ ;
ll ans = 0 ;
while (mp.rbegin() != mp.rend() && k) {
auto it = mp.rbegin();
auto nit = next(it);
ll v1 = it-> first;
ll n1 = it-> second;
ll v2 = 0 ;
if (nit != mp.rend())
v2 = nit-> first;
if ((v1 - v2) * n1 <= k) {
k -= (v1 - v2) * n1;
ans += cal(v2, v1) * n1;
mp.erase(v1);
if (v2)
mp[v2] += n1;
} else {
ll q = k / n1;
ll r = k % n1;
ans += cal(v1 - q, v1) * n1;
v1 -= q;
ans += v1 * r;
k = 0 ;
}
}
cout << ans << endl;
}
二分探索解法 楽しさが 0 以下のアトラクションに乗る意味はないから楽しさが 0 より大きい場合を考える。
問題は楽しさが $1, 2, \cdots, A_1, 1, 2, \cdots, A_2, \cdots, 1,2, \cdots, A_N$ である $\sum_i A_i$ 個のアトラクションから $K$ 個選んで乗ったときの楽しさの総和の最大値を求める問題と読み替えることができる。
Fri, Mar 14, 2025 D - aab aba baa https://atcoder.jp/contests/abc202/tasks/abc202_d
はじめの文字が a
となる場合の数は $A-1$ 個の a
と $B$ 個の b
の並び替え方の場合の数だから $\frac{(A+B+1)!}{(A-1)!B!}$ である。
よって $K > \frac{(A+B+1)!}{(A-1)!B!}$ のときは b
から始まる。
この性質を使って先頭から文字を決めていく。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll a, b, k;
cin >> a >> b >> k;
int m = a + b;
vvll dp(m + 1 , vll(m + 1 , 0 ));
dp[0 ][0 ] = 1 ;
rep(i, m + 1 ) {
rep(j, m + 1 ) {
if (i > 0 )
dp[i][j] += dp[i - 1 ][j];
if (j > 0 )
dp[i][j] += dp[i][j - 1 ];
}
}
string ans = "" ;
auto dfs = [& ](auto dfs, ll na, ll nb, ll rem) {
if (na == 0 && nb == 0 ) {
return ;
}
if (na == 0 ) {
ans.push_back('b' );
dfs(dfs, na, nb - 1 , rem);
return ;
}
if (nb == 0 ) {
ans.push_back('a' );
dfs(dfs, na - 1 , nb, rem);
return ;
}
ll v = dp[na - 1 ][nb];
if (rem <= v) {
ans.push_back('a' );
dfs(dfs, na - 1 , nb, rem);
return ;
}
ans.push_back('b' );
dfs(dfs, na, nb - 1 , rem - v);
};
dfs(dfs, a, b, k);
cout << ans << endl;
}
Thu, Mar 13, 2025 D - Journey https://atcoder.jp/contests/abc194/tasks/abc194_d
クーポンコレクター問題
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
double ans = 0 ;
rep2(i, 1 , n) {
ans += 1.0 / i;
}
printf("%.9lf \n " , ans * n);
}
Tue, Mar 11, 2025 A - Operations on a Stack https://atcoder.jp/contests/arc194/tasks/arc194_a
$d(i,1)$ を $i$ 番目の要素を使うときの総和の最大値、$d(i,0)$ を $i$ 番目の要素を使わないときの総和の最大値とする。
$i$ 番目の要素を使うとき、$i-1$ 番目の要素を使う場合、使わない場合の値いずれかの場合からの遷移がある。
$i$ 番目の要素を使わないとき、$i-1$ 番目の要素を積んで、その後その要素を捨てる操作になるから、$i-2$ 番目の要素を使う場合、使わない場合のうちの大きい方の値を選べばよい。
\begin{align}
d(i,1) &= \max(dp(i-1, 1), dp(i-1, 0)) + A_i \nonumber \\
d(i,0) &= \max(dp(i-2, 1), dp(i-2, 0)) \nonumber
\end{align}
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n;
cin >> n;
vll a(n + 1 );
rep(i, n) cin >> a[i + 1 ];
vvll dp(n + 1 , vll(2 , - INF));
dp[0 ][1 ] = 0 ;
rep2(i, 1 , n + 1 ) {
chmax(dp[i][1 ], dp[i - 1 ][1 ] + a[i]);
chmax(dp[i][1 ], dp[i - 1 ][0 ] + a[i]);
if (i - 2 >= 0 ) {
chmax(dp[i][0 ], dp[i - 2 ][1 ]);
chmax(dp[i][0 ], dp[i - 2 ][0 ]);
}
}
cout << max(dp[n][0 ], dp[n][1 ]) << '\n' ;
}
Mon, Mar 10, 2025 E - Our clients, please wait a moment https://atcoder.jp/contests/abc325/tasks/abc325_e
類題: https://atcoder.jp/contests/abc395/tasks/abc395_e
社用車だけを使ったときの場合のグラフ $G$ と電車だけを使った場合のグラフ $G^\prime$ を作り、$G$ の各ノードから同じ都市に対応する $G^\prime$ のノードへコスト 0 の辺を張る。
都市 $i$ に対応する $G$ におけるノード番号を $i$, $G^\prime$ におけるノードの番号を $N + i$ とする。
求める答えはノード 1 から出発してノード $N$ またはノード $2N$ に到達するまでの最小コストである。
($1 \rightarrow N+1$ への移動はコスト 0 でできるから出発は 1 から出発するケースだけ考えればよい)
struct Edge {
ll to, cost;
};
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, a, b, c;
cin >> n >> a >> b >> c;
vvll D(n, vll(n));
rep(i, n) rep(j, n) cin >> D[i][j];
vector< vector< Edge>> graph(n * 2 );
rep(i, n) rep(j, n) {
if (i == j)
continue ;
graph[i].push_back({j, D[i][j] * a});
graph[j].push_back({i, D[i][j] * a});
graph[n + i].push_back({n + j, D[i][j] * b + c});
graph[n + j].push_back({n + i, D[i][j] * b + c});
}
rep(i, n) graph[i].push_back({n + i, 0 });
vll dist(n * 2 , INF);
dist[0 ] = 0 ;
// cost, pos
using P = pair< ll, ll> ;
priority_queue< P, vector< P> , greater< P>> pq;
pq.push({0 , 0 });
while (pq.size()) {
auto [cost, now] = pq.top();
pq.pop();
if (dist[now] < cost)
continue ;
for (auto [to, cost] : graph[now]) {
if (dist[to] <= dist[now] + cost)
continue ;
dist[to] = dist[now] + cost;
pq.push({dist[to], to});
}
}
cout << min(dist[n - 1 ], dist[n * 2 - 1 ]) << '\n' ;
}
Sun, Mar 9, 2025 はじめに 木マスター養成講座 で普通のセグメント木ををソラでかけるようになったあとに、「遅延評価セグメント木をソラで書きたいあなたに 」 (以降ソラ版と呼ぶ)を読んで遅延評価セグメント木をソラでかけるようになった。
とはいえ、ソラで書くの面倒なので ACL に移行するために「AtCoder LibraryのLazy Segtreeの使い方 」を読んでみたものの、lazy 配列の使い方がソラ版と違っていて対応関係がよくわからなかったのでまとめる。
主な違いまとめ ソラ版は lazy node に入れた値を自分の data node に適用するが、ACL 版だと子の data node に適用している ソラ版は lazy node に配列が被覆している範囲分も含めて値が入っているが、ACL 版だと範囲分は含まれていない例区間加算で範囲に $x$ を加算する場合を考える ソラ版は範囲の長さが $n$ なら lazy node に $n \times x$ が入っているが、ACL 版だと $x$ が入っている ACL 版の場合は長さの情報は data node にもたせて lazy node のデータを data node に適用するときに使う ソラ版の eval
関数は ACL 版の mapping 関数と composition 関数を同時にやっているようなもの ACL 版を使うときの S
, op
, e
, F
, mapping
, composition
はそれぞれ何を表しているのか
Sun, Mar 9, 2025 C - Buy Balls https://atcoder.jp/contests/abc396/tasks/abc396_c
嘘解法で解いてしまった。配列の範囲外参照していたが AC してしまった。
黒・白ともに降順にソートしておく。
値が正の黒ボールをまず全て取る。その時の数を $N_b$ とする。
次に $N_b$ 個を超えないように正の値の白ボールを取る。その時の数を $N_w$ とする。
$N_w < N_b$ のときは、追加で白ボールをとっても価値は増えないのでこれ以上取る意味はない。
$N_w = N_b$ のときを考える。
このとき、白ボールを取得すると黒ボールの個数を超えてしまうため、黒ボールも同時に取得する必要がある。
よって、追加で白ボールを取ったほうが良い状況は、$B_{N_b+1} + W_{N_w+1} > 0$ が成り立つときである。
これをボールがなくなるか、条件を満たさなくなるまで繰り返す。
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
int n, m;
cin >> n >> m;
vll b(n), w(m);
rep(i, n) cin >> b[i];
rep(i, m) cin >> w[i];
sort(rall(b));
sort(rall(w));
ll ans = 0 ;
int nb = 0 ;
rep(i, n) {
if (b[i] >= 0 ) {
ans += b[i];
nb++ ;
}
}
int nw = 0 ;
while (nw < m && nb > nw && w[nw] > 0 ) { // コンテスト中のコードだと nw < m を入れ忘れた
ans += w[nw];
nw++ ;
}
while (nw < m && nb < n && w[nw] + b[nb] >= 0 ) {
ans += w[nw] + b[nb];
nw++ ;
nb++ ;
}
cout << ans << endl;
}
解説の想定解法
Sat, Mar 8, 2025 E - Mancala 2 https://atcoder.jp/contests/abc340/tasks/abc340_e
区間加算、一点取得の問題。ac-library を使うと lazy segment tree が必要になってセットアップが大変だが自分でセグメントツリーを実装すると意外と簡単。
かつっぱ氏の木マスター養成講座がわかりやすい
ref: https://youtu.be/g30mEb4jE2g?si=g-oLairJqPWzWHn6
struct SegmentTree {
vll seg;
int len;
SegmentTree(int n) {
rep(i, 30 ) {
if (n <= (1 << i)) {
len = 1 << i;
break ;
}
}
seg.resize(len * 2 , 0 );
}
ll get (int p) {
p += len;
ll ans = seg[p];
while (p / 2 ) {
p /= 2 ;
ans += seg[p];
}
return ans;
}
void add (int l, int r, ll x) {
l += len, r += len;
while (l < r) {
if (l & 1 ) {
seg[l] += x;
l++ ;
}
l /= 2 ;
if (r & 1 ) {
seg[r - 1 ] += x;
r-- ;
}
r /= 2 ;
}
}
};
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll n, m;
cin >> n >> m;
SegmentTree seg(n);
rep(i, n) {
ll a;
cin >> a;
seg.add(i, i + 1 , a);
}
rep(i, m) {
int b;
cin >> b;
ll x = seg.get(b);
seg.add(b, b + 1 , - x);
b++ ;
ll q = x / n;
ll r = x % n;
seg.add(0 , n, q);
if (b + r >= n) {
seg.add(b, n, 1 );
seg.add(0 , b + r - n, 1 );
} else {
seg.add(b, b + r, 1 );
}
}
rep(i, n) {
if (i != 0 )
cout << ' ' ;
cout << seg.get(i);
}
cout << endl;
}
Thu, Mar 6, 2025 E - Modulo MST $N = 8$ のときの完全グラフのときの辺の数は $\displaystyle \frac{N(N-1)}{2} = \frac{8 \times 7}{2} = 28$ でここから $N-1=7$ ほんの辺を選ぶ選び方は $_{28}\mathrm{C}_7 = 1,184,040$ 通りなので全探索が間に合う。
各頂点について自身を含めて他のどの頂点に辺を張るかということを考える場合でも $N^{N-1} = 8^7 = 2,097,152$ こっちの方法でも間に合う。
$N-1$ 本の辺を張る実装
void solve () {
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
ll n, m, k;
cin >> n >> m >> k;
vector< tuple< ll, ll, ll>> es;
rep(i, m) {
ll u, v, w;
cin >> u >> v >> w;
u-- , v-- ;
es.emplace_back(u, v, w);
}
ll ans = INF;
auto dfs = [& ](auto dfs, int depth, vint used) {
if (used.size() == n - 1 ) {
dsu uf(n);
ll sum = 0 ;
for (int i : used) {
auto [u, v, w] = es[i];
if (uf.same(u, v))
return ;
uf.merge(u, v);
sum += w;
sum %= k;
}
chmin(ans, sum);
return ;
}
if (depth == m) {
return ;
}
dfs(dfs, depth + 1 , used);
used.push_back(depth);
dfs(dfs, depth + 1 , used);
};
dfs(dfs, 0 , {});
cout << ans << endl;
}
辺が存在するかわからないがとりあえず考えうるパターンを確認する実装
Wed, Mar 5, 2025 D - Diagonal Separation https://atcoder.jp/contests/abc386/tasks/abc386_d
解説 AC
黒マスの座標を $(x,y)$ とすると $(i,j) \in [1,x] \times [1,y]$ のマスに白マスが存在しないことが条件である。
与えられたマスを辞書順に sort して上記の条件と合致するか確認する。
頭から順に確認していき
白マスならばそれまで出た白マスの中で最小の $y$ 座標を記録する 黒マスならばそれまで出た白マスの中で最小の $y$ 座標より大きいか確認する とすればよい。
void solve () {
int n, m;
cin >> n >> m;
vector< tuple< int , int , char >> qs;
rep(i, m) {
int x, y;
char c;
cin >> x >> y >> c;
qs.emplace_back(x, y, c);
}
sort(all(qs));
int cnum = INF;
for (auto [x, y, c] : qs) {
if (c == 'B' ) {
if (cnum <= y) {
yesno(false);
return ;
}
} else {
chmin(cnum, y);
}
}
yesno(true);
}
E - Maximize XOR https://atcoder.jp/contests/abc386/tasks/abc386_e
Sat, Mar 1, 2025 D - Pigeon Swap https://atcoder.jp/contests/abc395/tasks/abc395_d
想定と違う方法で解けたがすごく複雑なことをしてしまった。
提出コードでは鳩 $i$ が配置されたときの index とその時の巣の番号と、箱を swap したときの履歴を管理して解いた。
種別 1 のクエリで鳩を移動させたあとに箱の swap があった場合は、swap の履歴をたどって最終的な巣の番号を求めた。
void solve () {
int N, Q;
cin >> N >> Q;
// 配置されたときの index, その時の巣の番号
vector< pair< int , int >> hato(N);
// 入れ替えがあったときの index, 移動先の巣の番号
vector< vector< pair< int , int >>> hako(N);
rep(i, N) {
hato[i] = {- 1 , i};
hako[i].push_back({- 1 , i});
}
map< pair< int , int > , pair< int , int >> memo;
rep(i, Q) {
int t;
cin >> t;
if (t == 1 ) {
int a, b;
cin >> a >> b;
a-- , b-- ;
hato[a] = {i, b};
} else if (t == 2 ) {
int a, b;
cin >> a >> b;
a-- , b-- ;
hako[a].push_back({i, b});
hako[b].push_back({i, a});
} else {
int a;
cin >> a;
a-- ;
auto [id, num] = hato[a];
vector< pair< int , int >> olds;
while (true) {
auto it = upper_bound(all(hako[num]), make_pair(id, INF));
if (it == hako[num].end())
break ;
olds.push_back(* it);
if (memo.count(* it)) {
auto x = memo[* it];
id = x.first;
num = x.second;
} else {
id = it-> first;
num = it-> second;
}
}
if (olds.size())
olds.pop_back();
// 経路圧縮
for (auto [a, b] : olds) {
memo[{a, b}] = {id, num};
}
hato[a] = {id, num};
cout << num + 1 << endl;
}
}
}
解説放送を見たあとの復習
Sat, Mar 1, 2025 E - Xor Sigma Problem https://atcoder.jp/contests/abc365/tasks/abc365_e
$\sum_{i,j} f(i,j)$ 系の問題は $i \leq j$ に関する $f(i,j)$ の値を表に書いてみて規則性を見つけると解けることが多い。
\begin{align}
f(i,j) &= A_i \oplus A_{i+1} \oplus \cdots \oplus A_j \nonumber \\
S_i &= \sum_{j=i}^N f(i,j) \nonumber
\end{align}
と定義する。
表: $f(i,j)$ の値
\begin{align}
\sum_{i=1}^{N-1} \sum_{j=i+1}^N f(i,j) &= \sum_{i=1}^{N} \left\{ \left( \sum_{j=i}^{N} f(i,j) \right) - f(i,i) \right\} \nonumber \\
&= \sum_{i=1}^{N} \left\{ \left( \sum_{j=i}^{N} f(i,j) \right) - A_i \right\} \nonumber \\
&= \sum_{i=1}^{N} S_i - A_i \nonumber
\end{align}
Fri, Feb 28, 2025 E - Distance Sequence https://atcoder.jp/contests/abc253/tasks/abc253_e
$f(i,j)$ を $A_i = j$ となる場合の数とすると
\begin{align}
f(1,j) &= 1 ~~~(\forall j \in [1,M]) \nonumber \\
f(i+1,j) &= \sum_{k=1}^{j-K} f(i,k) + \sum_{k=j+K}^{M} f(i,k) \nonumber
\end{align}
となる。naive に実装すると以下のようになり、計算量が $O(NM^2)$ となるため問題の制約では TLE になる。
vector< vector< mint>> dp(N + 1 , vector< mint> (M + 1 , 0 ));
rep(i, M) dp[1 ][i + 1 ] = 1 ;
rep2(i, 2 , N + 1 ) {
rep2(k, 1 , M + 1 ) {
rep2(j, 1 , M + 1 ) {
if (abs(k - j) >= K)
dp[i][k] += dp[i - 1 ][j];
}
}
}
mint ans = 0 ;
rep2(i, 1 , M + 1 ) ans += dp[N][i];
cout << ans.val() << endl;
ここで dp[i-1]
の累積和計算しておくことで一番内側の loop 内の処理を $O(1)$ で行うことができる。
$K = 0$ のときに dp[i-1][j]
からの寄与をダブルカウントしないように注意する
Fri, Feb 28, 2025 E - I Hate Sigma Problems https://atcoder.jp/contests/abc371/tasks/abc371_e
sample 2 の $f(i,j)$ の値を表にまとめると以下のようになる.
A_i \ A_j 5 4 2 2 3 2 4 4 1 5 1 2 3 3 4 4 4 4 5 4 1 2 2 3 3 3 3 4 2 1 1 2 2 3 3 4 2 1 2 2 3 3 4 3 1 2 3 3 4 2 1 2 2 3 4 1 1 2 4 1 2 1 1
この表より各行の和の値は以下のように遷移することが推察される。
Mon, Feb 24, 2025 E - Palindromic Shortest Path https://atcoder.jp/contests/abc394/tasks/abc394_e
解説 AC
短い回分から両端に同じ文字を追加していくことで回分の長さを増やしていく。
void solve () {
int n;
cin >> n;
vector< string> delta(n);
rep(i, n) cin >> delta[i];
vvll dist(n, vll(n, - 1 ));
// 回分の最短距離が決まっている path を que に入れる
queue< pair< int , int >> que;
rep(i, n) {
dist[i][i] = 0 ;
que.push({i, i});
}
rep(i, n) rep(j, n) {
if (i != j && delta[i][j] != '-' ) {
dist[i][j] = 1 ;
que.push({i, j});
}
}
while (que.size()) {
auto [i, j] = que.front();
que.pop();
rep(k, n) {
rep(l, n) {
if (delta[k][i] != '-' && delta[k][i] == delta[j][l] &&
dist[k][l] == - 1 ) {
// i -> j への path の回分の長さはすでに決まっている
// delta[k][i] == delta[j][l] ならば、k -> i -> j -> l という path が回分になる
// que には距離が短い順に入っているので一番最初に見つかった k -> l の path を最短として良い
// k -> i -> j -> l なる path を考えるときに (i,j) の全ての組み合わせを調べる必要はないということ
dist[k][l] = dist[i][j] + 2 ;
que.push({k, l});
}
}
}
}
for (auto v : dist)
print(v);
}
Sun, Feb 23, 2025 D - Island Tour https://atcoder.jp/contests/abc338/tasks/abc338_d
解説 AC
void solve () {
ll n, m;
cin >> n >> m;
vll x(m);
rep(i, m) {
cin >> x[i];
x[i]-- ;
}
vll cumsum(n * 2 + 1 , 0 );
rep(i, m - 1 ) {
ll mi = min(x[i], x[i + 1 ]);
ll mx = max(x[i], x[i + 1 ]);
ll dr = mx - mi;
ll dl = n - dr;
cumsum[mi] += dl;
cumsum[mx] -= dl;
cumsum[0 ] += dr;
cumsum[mi] -= dr;
cumsum[mx] += dr;
cumsum[n] -= dr;
}
rep(i, n * 2 ) cumsum[i + 1 ] += cumsum[i];
ll ans = INF;
rep(i, n) chmin(ans, cumsum[i]);
cout << ans << endl;
}
Thu, Feb 20, 2025 E - Round Trip https://atcoder.jp/contests/abc276/tasks/abc276_e
start から BFS をする。
start から右移動から始めたところには 1 start から左移動から始めたところには 2 start から上移動から始めたところには 3 start から下移動から始めたところには 4 といった具合に番号を振っていく。(番号が異なれば良いので上下左右と番号の対応が上記と違っても問題ない)
異なる番号が隣り合うところがあれば異なる経路で同じ場所にたどり着けるということなので Yes、そのような場所がなければ No を出力する。
sample 1 の場合の番号の付け方の例は以下のよう
3 3 3 1
-1 3 -1 1
2 0 1 1
2 -1 -1 1
void solve () {
int H, W;
cin >> H >> W;
vector< string> grid(H);
rep(i, H) cin >> grid[i];
int si, sj;
rep(i, H) rep(j, W) if (grid[i][j] == 'S' ) si = i, sj = j;
vvint mark(H, vint(W, - 1 ));
queue< pair< int , int >> q;
q.push({si, sj});
mark[si][sj] = 0 ;
vint di = {0 , 1 , 0 , - 1 };
vint dj = {1 , 0 , - 1 , 0 };
while (q.size()) {
auto [i, j] = q.front();
q.pop();
int c = mark[i][j];
int start = c == 0 ;
rep(d, 4 ) {
if (start)
c = d + 1 ;
int ni = i + di[d], nj = j + dj[d];
if (clamp(ni, 0 , H - 1 ) != ni || clamp(nj, 0 , W - 1 ) != nj)
continue ;
if (grid[ni][nj] == '#' )
continue ;
int m = mark[ni][nj];
if (m > 0 && m != c) {
yesno(true);
return ;
}
if (m == - 1 ) {
mark[ni][nj] = c;
q.push({ni, nj});
}
}
}
yesno(false);
}
Tue, Feb 18, 2025 D - Swap to Gather なんとなく中央の 1 に寄せると良さそうという雰囲気で実装して AC してしまった
void solve () {
int N;
string S;
cin >> N >> S;
vint ids;
rep(i, N) {
if (S[i] == '1' )
ids.push_back(i);
}
ll ans = 0 ;
int mid = ids[ids.size() / 2 ];
int zid = mid + 1 ;
for (int i = mid + 1 ; i < N; i++ ) {
while (S[zid] == '1' && zid < N)
zid++ ;
if (i <= zid)
continue ;
if (S[i] == '1' ) {
ans += i - zid;
swap(S[i], S[zid]);
}
}
zid = mid - 1 ;
for (int i = mid - 1 ; i >= 0 ; i-- ) {
while (S[zid] == '1' && zid >= 0 )
zid-- ;
if (zid <= i)
continue ;
if (S[i] == '1' ) {
ans += zid - i;
swap(S[i], S[zid]);
}
}
cout << ans << endl;
// cout << S << endl;
}
E - GCD of Subset 愚直に全ての $A_i$ についての約数を調べて出現回数をメモって、各 $i$ に対して $A_i$ の約数のうち出現回数が $K$ を超えている中で最大のものを出力とすると TLE ぎりぎりだがなんとか AC できた。
しかし、どうやら想定解ではなかったらしい。
Sun, Feb 16, 2025 D - Grid and Magnet https://atcoder.jp/contests/abc351/tasks/abc351_d
3時間近くかかったが自力 AC
磁石が置かれていないマスを空マスと呼ぶことにする。
磁石と隣接していない空マス同士を union-find で連結する。
連結部分のサイズと連結部分の外縁にある磁石と隣接する空マスの数の合計のうち最大値を出力する。
int h, w;
vector< string> grid;
vvint memo;
vvint visited;
vint di = {0 , 1 , 0 , - 1 };
vint dj = {1 , 0 , - 1 , 0 };
pair< int , int > ind2sub(int x) {
return {x / w, x % w};
}
int sub2ind (int i, int j) {
return i * w + j;
}
int canMove (int i, int j) {
int ok = 1 ;
rep(d, 4 ) {
int ni = i + di[d], nj = j + dj[d];
if (clamp(ni, 0 , h - 1 ) != ni || clamp(nj, 0 , w - 1 ) != nj)
continue ;
if (grid[ni][nj] == '#' )
ok = 0 ;
}
return ok;
}
void solve () {
cin >> h >> w;
grid = vector< string> (h);
rep(i, h) cin >> grid[i];
dsu uf(h * w);
rep(i, h) rep(j, w) {
if (grid[i][j] == '.' && canMove(i, j)) {
for (auto [ii, jj] : vector< pair< int , int >> ({{1 , 0 }, {0 , 1 }})) {
int ni = i + ii, nj = j + jj;
if (clamp(ni, 0 , h - 1 ) != ni || clamp(nj, 0 , w - 1 ) != nj)
continue ;
if (canMove(ni, nj)) {
uf.merge(sub2ind(i, j), sub2ind(ni, nj));
}
}
}
}
int ans = 1 ;
for (auto v : uf.groups()) {
set< pair< int , int >> stop;
{
auto [i, j] = ind2sub(v[0 ]);
if (grid[i][j] == '#' )
continue ;
else if (! canMove(i, j)) {
continue ;
}
}
for (int id : v) {
auto [i, j] = ind2sub(id);
rep(d, 4 ) {
int ni = i + di[d], nj = j + dj[d];
if (clamp(ni, 0 , h - 1 ) != ni || clamp(nj, 0 , w - 1 ) != nj)
continue ;
if (grid[ni][nj] == '.' && ! canMove(ni, nj))
stop.insert({ni, nj});
}
}
chmax(ans, (int )v.size() + (int )stop.size());
}
cout << ans << endl;
}
Sat, Feb 15, 2025 D - Palindromic Number https://atcoder.jp/contests/abc363/tasks/abc363_d
解説 AC
ll intpow (ll x, ll n) {
long long ret = 1 ;
while (n > 0 ) {
if (n & 1 )
ret *= x;
x *= x;
n >>= 1 ;
}
return ret;
}
void solve () {
ll N;
cin >> N;
if (N == 1 ) {
cout << 0 << endl;
return ;
}
N-- ;
for (ll d = 1 ;; d++ ) {
if (N <= intpow(10 , (d - 1 ) / 2 ) * 9 ) {
ll b = intpow(10 , (d - 1 ) / 2 ) + N - 1 ;
string s = to_string(b);
s.resize(s.size() * 2 - d % 2 , ' ' );
rep(i, d / 2 ) {
s[s.size() - i - 1 ] = s[i];
}
cout << s << endl;
return ;
} else {
N -= intpow(10 , (d - 1 ) / 2 ) * 9 ;
}
}
}
Fri, Feb 14, 2025 D - Divide Interval https://atcoder.jp/contests/abc349/tasks/abc349_d
証明せずに貪欲で AC した。
左端の数から始まる良い数列のうち最長のものを採用していく。
左端の数が $l = 2^i j$ とするとき、良い数列は次の通りである
長さ 数列 $2^i$ $S(2^i j, 2^i (j+1))$ $2^{i-1}$ $S(2^{i-1} 2j, 2^{i-1} 2(j+1))$ $\vdots$ $1$ $S(2^1 2^{i-1}j, 2^1 2^{i-1}(j+1))$ $0$ $S(2^0 2^{i}j, 2^0 2^{i}(j+1))$
このうち $R$ を超えないもので最長のものを採用していく。
void solve () {
ll l, r;
cin >> l >> r;
vector< pair< ll, ll>> ans;
while (r - l >= 1 ) {
ll cnt = 0 ;
while ((l & (1LL << cnt)) == 0 && cnt <= 60 )
cnt++ ;
while (l + (1LL << cnt) > r)
cnt-- ;
ans.push_back({l, l + (1LL << cnt)});
l += 1LL << cnt;
}
cout << ans.size() << endl;
for (auto [x, y] : ans) {
cout << x << ' ' << y << endl;
}
}
Wed, Feb 12, 2025 D - Minimum Steiner Tree https://atcoder.jp/contests/abc368/tasks/abc368_d
$V_1$ から DFS して、子または自身が $V_i$ に含まれていればそのノードを残す。
vvint graph;
vint visited;
set< int > memo;
int ans = 0 ;
bool dfs (int now) {
bool res = false;
if (memo.count(now))
res = true;
for (int nx : graph[now]) {
if (visited[nx])
continue ;
visited[nx] = 1 ;
bool x = dfs(nx);
res = res || x;
// short-circuit で以下のよう書くと dfs が実行されない
// res = res || dfs(nx)
}
if (res)
ans++ ;
return res;
}
void solve () {
int n, k;
cin >> n >> k;
graph = vvint(n);
rep(i, n - 1 ) {
int u, v;
cin >> u >> v;
u-- , v-- ;
graph[u].push_back(v);
graph[v].push_back(u);
}
vint nodes(k);
rep(i, k) {
cin >> nodes[i];
nodes[i]-- ;
}
for (int x : nodes)
memo.insert(x);
visited = vint(n, 0 );
visited[nodes[0 ]] = 1 ;
dfs(nodes[0 ]);
cout << ans << endl;
}
Wed, Feb 12, 2025 D - Count Bracket Sequences https://atcoder.jp/contests/abc312/tasks/abc312_d
解説 AC
Fri, Feb 7, 2025 D - Only one of two https://atcoder.jp/contests/abc341/tasks/abc341_d
解説 AC
Thu, Feb 6, 2025 D - Three Days Ago 解説 AC
void solve () {
string S;
cin >> S;
int n = S.size();
vvll v(n + 1 , vll(10 , 0 ));
rep(i, n) {
v[i + 1 ][S[i] - '0' ]++ ;
}
rep(k, 10 ) {
rep(i, n) {
v[i + 1 ][k] += v[i][k];
v[i + 1 ][k] %= 2 ;
}
}
vll cnt(1 << 10 , 0 );
rep2(i, 0 , n + 1 ) {
int x = 0 ;
rep(k, 10 ) {
x *= 2 ;
if (v[i][k])
x += 1 ;
}
cnt[x]++ ;
}
ll ans = 0 ;
rep(i, 1 << 10 ) {
ans += cnt[i] * (cnt[i] - 1 ) / 2 ;
}
cout << ans << endl;
}
Tue, Feb 4, 2025 D - Pyramid https://atcoder.jp/contests/abc336/tasks/abc336_d
解説 AC.
void solve () {
ll N;
cin >> N;
vll A(N);
rep(i, N) cin >> A[i];
A.push_back(0 );
A.insert(A.begin(), 0 );
N += 2 ;
vll dl(N, 0 ), dr(N, 0 );
rep2(i, 1 , N) {
dl[i] = min(dl[i - 1 ] + 1 , A[i]);
}
rep(i, N - 1 ) {
int k = N - 1 - i;
dr[k - 1 ] = min(dr[k] + 1 , A[k - 1 ]);
}
ll ans = 0 ;
rep(i, N) {
chmax(ans, min(dr[i], dl[i]));
}
cout << ans << endl;
}
Tue, Feb 4, 2025 E - Grid Filling https://atcoder.jp/contests/abc278/tasks/abc278_e
$\mathrm{ rows }(i, m)$ を 行番号が $[0,i)$ の範囲(0-index)での数字 $m$ の出現回数、$\mathrm{ cols }(j,m)$ を列番号が $[0,i)$ の範囲での数字 $m$ の出現回数とする。
$[i,i+h)$, $[j,j+w)$ の領域が塗りつぶされたときに、残りのマスに数字 $m$ の出現する場合以下の式の少なくとも1つが true となる。
\begin{align}
&\mathrm{ cols }(j-1,m) > 0 \\
&\mathrm{ cols }(W,m) - \mathrm{ cols }(j+w-1,m)) > 0 \\
&\mathrm{ rows }(i-1,m) > 0 \\
&\mathrm{ rows }(H, m) - \mathrm{ rows }(i+h-1,m) > 0
\end{align}
eq (1): 塗りつぶしたマスより左側の領域に対応 eq (2): 塗りつぶしたマスより右側の領域に対応 eq (3): 塗りつぶしたマスより上側の領域に対応 eq (4): 塗りつぶしたマスより下側の領域に対応 出現する数字は $[1,300]$ の範疇なので愚直に全通り出現するか確認し、出現した数の種類数を出力する。
Tue, Feb 4, 2025 A - mod M Game 2 解説 AC だが、かなり解像度が低い。
$\displaystyle \sum_{k=1}^{N} k = \frac{1}{2}N(N+1)$ より両者のカードの総和は $N(N+1)$。
最後の Bob の出す手が $b$ のとき Alice が最後に出した手までの総和は $N(N+1) - b$。
$N(N+1) - b$ が $0$ にできたら Bob の勝ち、それ以外のときは Alice の勝ち。
\begin{align}
N(N+1) - b &\equiv 0 \mod M \\
N(N+1) &\equiv b \mod M \\
\end{align}
$b \in [1,N]$ より、$1 \leq (N(N+1) \mod M) \leq N$ のときは Bob の勝ち。それ以外のときは Alice の勝ち
void solve () {
ll T;
cin >> T;
rep(_, T) {
ll N, M;
cin >> N >> M;
ll x = N * (N + 1 ) % M;
string ans = "Alice" ;
if (clamp(x, 1LL , N) == x) {
ans = "Bob" ;
}
cout << ans << endl;
}
}
Mon, Feb 3, 2025 D - M<=ab https://atcoder.jp/contests/abc296/tasks/abc296_d
解説 AC。
条件より $X = ab \wedge X \geq M$ である。また $a,b$ は対称だから $a \leq b$ としても答えは変わらない。
\begin{align}
M \leq X &= ab \Leftrightarrow \nonumber \\
\frac{M}{a} &\leq b \nonumber \\
\biggl\lceil \frac{M}{a} \biggr\rceil &\leq b \nonumber \\
\end{align}
$\lceil M/a \rceil$ は $b$ の下界だから $a \leq b$ と合わせて
\begin{align}
&a \leq \biggl\lceil \frac{M}{a} \biggr\rceil \nonumber < \frac{M}{a} + 1 \nonumber \\
\Leftrightarrow ~ &a(a-1) < M \nonumber
\end{align}
Sun, Feb 2, 2025 D - Factorial and Multiple https://atcoder.jp/contests/abc280/tasks/abc280_d
解説 AC
ルジャンドルの定理と二分探索で解く
ref: 予備ノリ 一度聞いたら忘れない『ルジャンドルの定理』の授業
ll f (ll n, ll p) {
ll ans = 0 ;
ll b = p;
while (n / b) {
ans += n / b;
b *= p;
}
return ans;
}
void solve () {
ll K;
cin >> K;
vector< pair< ll, ll>> div;
{
ll x = K;
for (ll i = 2 ; i * i <= x; i++ ) {
if (x % i != 0 )
continue ;
ll cnt = 0 ;
while (x % i == 0 ) {
x /= i;
cnt++ ;
}
div.emplace_back(i, cnt);
}
if (x != 1 )
div.emplace_back(x, 1 );
}
ll ac = K, wa = 1 ;
while (abs(ac - wa) > 1 ) {
ll wj = (ac + wa) / 2 ;
ll ok = 1 ;
for (auto [p, cnt] : div) {
if (f(wj, p) < cnt)
ok = 0 ;
}
if (ok)
ac = wj;
else
wa = wj;
}
cout << ac << endl;
}
Sun, Feb 2, 2025 D - Buildings 解説 AC.
void solve () {
ll n;
cin >> n;
vll h(n);
rep(i, n) cin >> h[i];
vll ans(n);
vll st;
for (int i = n - 1 ; i >= 0 ; i-- ) {
ans[i] = st.size();
while (st.size() != 0 && st.back() < h[i]) {
st.pop_back();
}
st.push_back(h[i]);
}
print(ans);
}
E - K-th Largest Connected Components https://atcoder.jp/contests/abc372/tasks/abc372_e
解説 AC
void solve () {
ll N, Q;
cin >> N >> Q;
dsu uf(N);
vvint nodes(N);
rep(i, N) {
nodes[i].push_back(i);
}
rep(i, Q) {
int q, a, b;
cin >> q >> a >> b;
if (q == 1 ) {
int u = a - 1 , v = b - 1 ;
if (uf.same(u, v))
continue ;
int ou = uf.leader(u);
int ov = uf.leader(v);
int newl = uf.merge(u, v);
vint tmp = nodes[ou];
for (auto x : nodes[ov])
tmp.push_back(x);
sort(rall(tmp));
vint newv;
for (int i = 0 ; i < 10 && i < tmp.size(); i++ ) {
newv.push_back(tmp[i]);
}
nodes[newl] = newv;
} else {
int v = a - 1 , k = b;
int l = uf.leader(v);
if (nodes[l].size() >= k) {
cout << nodes[l][k - 1 ] + 1 << endl;
} else {
cout << - 1 << endl;
}
}
}
}
Sun, Feb 2, 2025 B - Special Subsets 条件より $i$ から $f(i)$ に辺を張ったグラフを作り、このうちサイクルを成しているサブグラフの頂点集合が $T$ になる。
各サブグラフは独立しているからサイクルを成しているサブグラフの個数を $n$ とすると $2^n - 1$ が答え。
void solve () {
ll N;
cin >> N;
vll f(N);
rep(i, N) {
cin >> f[i];
f[i]-- ;
}
scc_graph graph(N);
rep(i, N) {
graph.add_edge(i, f[i]);
}
vvint scc = graph.scc();
ll cnt = 0 ;
for (auto & x : scc) {
if (x.size() > 1 ) {
cnt++ ;
} else {
// 大きさ1のときは自己ループしていればカウント
if (x[0 ] == f[x[0 ]])
cnt++ ;
}
}
mint ans = ((mint)2 ).pow(cnt) - 1 ;
cout << ans.val() << endl;
}
Sun, Feb 2, 2025 E - Mex and Update https://atcoder.jp/contests/abc330/tasks/abc330_e
解説 AC
Sun, Feb 2, 2025 E - Destruction https://atcoder.jp/contests/abc218/tasks/abc218_e
$C_i$ が小さい順にクラスカル法で最小全域木を作る。
使わない辺のうち、コストが負のものは捨てて、コストが正のものを答えとして計上していく。
計上しない負のコストの辺はグラフから取り除かずそのままにしている辺に相当する。
void solve () {
ll N, M;
cin >> N >> M;
vector< Edge> es(M);
rep(i, M) {
ll a, b, c;
cin >> a >> b >> c;
a-- , b-- ;
es[i] = {a, b, c};
}
priority_queue< Edge, vector< Edge> , greater< Edge>> pq;
for (auto e : es)
pq.push(e);
dsu uf(N);
vll dust;
while (pq.size()) {
auto [u, v, c] = pq.top();
pq.pop();
if (uf.same(u, v)) {
if (c > 0 )
dust.push_back({c});
} else {
uf.merge(u, v);
}
}
ll ans = 0 ;
for (ll x : dust) {
if (x > 0 )
ans += x;
}
cout << ans << endl;
}
Sun, Feb 2, 2025 D - Gravity https://atcoder.jp/contests/abc391/tasks/abc391_d
嘘解法で通してしまった。$x$ が同じ時ブロックではあとからのほうが $y$ が大きいとしてしまったがそんな制約はなかった。
$y$ に sort 入れなかったときにバグる入力例
4 2
1 2
1 1
2 1
2 2
2
1 2
1 3
解法 列 $x$ のあるブロックのうち下から $j$ 番目のブロックの $y$ 座標を $y_{x,j}$ とする。
$k$ 回目の行が消える操作は $t_k = \max \\{ y_{x,k} | x \in [1,W], x \in \mathbb{Z} \\}$ の時刻に実行される。
このような $y_{x,k}$ が存在しない場合は $t_k = \infty$ とする。
ブロック $i$ が 列 $X_i$ の下から k 番目のブロックであるとき、消えるのは時刻 $t_k$ のときである。
これらの情報からクエリの時刻においてブロック $i$ が存在しているかどうかを判定する。
Fri, Jan 31, 2025 D - A Piece of Cake ピースの数は多いので愚直に全探索はできない。
イチゴが入るピースの数は少ないので各イチゴがどのピースに入るかという基準で考える。
イチゴが入るピースの右下の座標をそのピースの代表としてそのピースに入るイチゴの数を数えばよい。イチゴがどのピースに属するかはそのイチゴのすぐ右と下にある分割線を二分探索で探せば良い。
void solve () {
ll W, H, N;
cin >> W >> H >> N;
vll p(N), q(N);
rep(i, N) {
cin >> p[i] >> q[i];
}
ll na;
cin >> na;
vll a(na + 1 );
rep(i, na) {
cin >> a[i];
}
a.back() = W;
ll nb;
cin >> nb;
vll b(nb + 1 );
rep(i, nb) {
cin >> b[i];
}
b.back() = H;
// イチゴが入るピースの右下の座標
map< pair< ll, ll> , ll> cnt;
rep(i, N) {
ll rh, rw;
rh = * lower_bound(all(a), p[i]);
rw = * lower_bound(all(b), q[i]);
cnt[{rh, rw}]++ ;
}
// ピースの数が最大数より少なかったらイチゴが 0 個のピースを sentinel
// として一つ追加
if ((ll)cnt.size() < (na + 1 ) * (nb + 1 ))
cnt[{- 1 , - 1 }] = 0 ;
ll maxv = 0 , minv = INF;
for (auto it = cnt.begin(); it != cnt.end(); it++ ) {
chmin(minv, it-> second);
chmax(maxv, it-> second);
}
cout << minv << ' ' << maxv << endl;
}
Fri, Jan 31, 2025 D - Popcount and XOR 考察 自力で解けたが1日考えた。
$c = \mathrm{popcount}(C)$ とする。$a
$X \oplus Y = C$ のことは一旦忘れ $\mathrm{popcount}(X) = a, \mathrm{popcount}(Y) = b$ だけを考える。
$X$, $Y$ で極力同じ場所の bit を立てた場合 $\mathrm{popcount}(X \oplus Y) = |a-b|$。
$X$, $Y$ で同じ場所に bit を建てなかった場合 $\mathrm{popcount}(X \oplus Y) = a+b$ であるから $X \oplus Y = C$ になる必要条件は
$$
|a-b| \leq c \leq a+b
$$である。また同じ場所に立っていた bit を別の場所で立てた場合 popcount の値は2ずつ変わるので $a+b$ と $c$ の parity は同じにならなければならない。
たとえば、下記のように $X$ の 2 bit 目の bit を下げて 4 bit 目を上げると xor を取ると popcount の値が2増える。逆に $X,Y$ のどちらかだけに立っていた bit の位置を1つの場所に移動させると popcount の値が 2 減る。
\begin{align}
X = 00111 \Rightarrow X^\prime &= 10011 \nonumber \\
Y &= 01111 \nonumber
\end{align}
Fri, Jan 31, 2025 D - General Weighted Max Matching $N$ が奇数のとき、頂点を一つ追加してその頂点との辺の重みは $0$ としても結果は変わらない。よって以後 $N$ は偶数として扱う。
2頂点ずつ $N/2$ 組に分ける組み合わせの数は
$$
\frac{\binom{n}{2} \times \binom{n-2}{2} \times \cdots \times \binom{2}{2}}{2^{N/2} (N/2)!}
$$である。$N=16$ のとき $\displaystyle \frac{16!}{2^8 8!} = 2,027,025$ なので全探索しても十分間に合う。
実装をどうするかを考える。まずは $N = 4$ という具体例について考えてみる。
このときの分け方は次の3通りが考えられる。
$(1, 2), (3, 4)$ $(1, 3), (2, 4)$ $(1, 4), (2, 3)$ これより
まだ選んでいないものから一番 index が若いものを選ぶ 相方はそれ以降の index からまだ選んでいないものを選ぶ 次の組についても同様にして選んで行く N/2 組みできた時点で終了する という再帰関数を書けば全探索ができる。
ll N, m;
vvll D (16 , vll(16 , 0 ));
ll ans = 0 ;
void dfs (int state, int depth, ll sum) {
if (depth == (N + 1 ) / 2 ) {
chmax(ans, sum);
}
int l = 0 ;
rep(i, m) if (! (state & (1 << i))) {
l = i;
state ^= (1 << i);
break ;
}
rep2(i, l + 1 , m) {
if (! (state & (1 << i))) {
state ^= (1 << i);
dfs(state, depth + 1 , sum + D[l][i]);
state ^= (1 << i);
}
}
}
void solve () {
cin >> N;
m = N + N % 2 ;
rep(i, N) {
rep2(j, i + 1 , N) {
cin >> D[i][j];
}
}
dfs(0 , 0 , 0 );
cout << ans << endl;
}
E - Sandwiches https://atcoder.jp/contests/abc318/tasks/abc318_e
Wed, Jan 29, 2025 D - Marking 方針がなかなか決まらなくて諦めかけたがなんとか自力 AC。
$x_k$ を $k$ 番目に印をつけられるマスの番号とする。
すでに印がつけられていたとしても i
の操作で移動したとすると
\begin{align}
x_1 &= 0 \nonumber \\
x_2 &= x_1 + D \mod N \nonumber \\
x_3 &= x_2 + D \mod N \nonumber \\
\vdots \nonumber \\
x_k &= x_{k-1} + D \mod N \nonumber \\
\end{align}
これより、$x_k = x_1 + (k-1)D \mod N$ ということがわかる。
操作 ii
が初めて起こるのは $\alpha D \equiv 0 \mod N$ となる最小の正の整数 $\alpha$ のときである。すなわち、$\alpha D$ が $N$ の倍数となるような最小の正の整数 $\alpha$ を見つけることができれば周期がわかる。よって操作 ii
が何回行われたのかもわかる。
Wed, Jan 29, 2025 E - Clique Connect https://atcoder.jp/contests/abc352/tasks/abc352_e
最小全域木を構成するアルゴリズムであるクラスカル法を思いつけると解ける。
クラスカル法は辺のコストが小さい順に閉路ができないように辺を採用していくアルゴリズムである。
$C_i$ が小さい部分集合から辺を構成し順にクラスカル法で辺を張っていく。このとき、部分集合内の頂点同士が連結になりさえすれば良いので
$\\{(A_1, A_j)| j \in [2, K_i], j \in \mathbb{Z} \\}$ という辺だけを考えるだけで良い。
最終的に連結グラフになっていたらそのコストを出力し、なってなかったら $-1$ を出力する。
void solve () {
ll N, M;
cin >> N >> M;
vvll A(M);
vll C(M);
rep(i, M) {
ll k, c;
cin >> k >> c;
C[i] = c;
rep(j, k) {
ll a;
cin >> a;
a-- ;
A[i].push_back(a);
}
}
vector< pair< ll, int >> cs;
rep(i, M) {
cs.push_back({C[i], i});
}
sort(all(cs));
dsu uf(N);
ll ans = 0 ;
for (auto [c, i] : cs) {
int k = A[i].size();
rep2(j, 1 , k) {
int a = A[i][0 ], b = A[i][j];
if (uf.same(a, b))
continue ;
uf.merge(a, b);
ans += c;
}
}
if (uf.groups().size() != 1 )
ans = - 1 ;
cout << ans << endl;
}
Wed, Jan 29, 2025 B - New Place https://atcoder.jp/contests/arc154/tasks/arc154_b
$S$ の $i$ 文字目、$T$ の $j$ 文字目をそれぞれ $s_i$, $t_j$ と書くことにする。
削除した文字は一旦プールに入れておくということにする。
後ろから文字を比較していって
一致した場合 一致しなかった場合プールにある場合 プールにない場合その文字を調達してこないといけないので調達したい文字を含めて $S$ の先頭の文字を削除する 削除した回数をカウントしつつ、削除した文字はプールに入れておく。ただし調達対象の文字はプールに入れない 次の文字を比較する void solve () {
int N;
cin >> N;
string S, T;
cin >> S >> T;
ll ans = 0 ;
map< char , int > store;
int tl = N - 1 , sh = 0 , sl = N - 1 ;
while (tl >= 0 && sl >= 0 && sh <= sl) {
if (S[sl] == T[tl]) {
tl-- , sl-- ;
} else if (store[T[tl]]) {
store[T[tl]]-- ;
tl-- ;
} else {
while (sh <= sl) {
ans++ ;
if (S[sh] == T[tl]) {
sh++ , tl-- ;
break ;
} else {
store[S[sh]]++ ;
sh++ ;
}
}
if (sh > sl) {
cout << - 1 << endl;
return ;
}
}
}
cout << ans << endl;
}
Wed, Jan 29, 2025 D - Max Multiple https://atcoder.jp/contests/abc281/tasks/abc281_d
$dp(i,j,r)$ を $i$ 番目までの数字から $j$ 個使ってできた総和を D で割った余りが $r$ のときの総和の最大値とする
void solve () {
ll N, K, D;
cin >> N >> K >> D;
vll A(N + 1 );
rep(i, N) cin >> A[i + 1 ];
// dp[i][j][r]: i 番目までの数字から j 個を選んだときの和を D
// で割ったあまりが r のときの和の最大値
vector< vvll> dp(N + 1 , vvll(K + 1 , vll(D, - INF)));
dp[0 ][0 ][0 ] = 0 ;
rep2(i, 1 , N + 1 ) {
rep(j, K + 1 ) {
// i 番目の数字を使わない
dp[i][j] = dp[i - 1 ][j];
// i 番目の数字を使う
if (j > 0 ) {
rep(r, D) {
if (dp[i - 1 ][j - 1 ][r] < 0 )
continue ;
ll x = dp[i - 1 ][j - 1 ][r] + A[i];
chmax(dp[i][j][x % D], x);
}
}
}
}
ll ans = dp[N][K][0 ];
if (ans < 0 )
ans = - 1 ;
cout << ans << endl;
}
Wed, Jan 29, 2025 E - Set Meal https://atcoder.jp/contests/abc331/tasks/abc331_e
解説 AC
2025/2/23 retry
副菜を降順にソートしておく。主菜を固定したときに選べる副菜のうち一番高いものを採用する。全ての主菜に対して同じことをしてそのうちの最大を出力。
選ぶ必要のない副菜の検索を飛ばすことで $N+L$ 回程度の比較しかされないので十分早い。
void solve () {
ll n, m, l;
cin >> n >> m >> l;
vll a(n), b(m), c(l), d(l);
rep(i, n) cin >> a[i];
rep(i, m) cin >> b[i];
rep(i, l) {
cin >> c[i] >> d[i];
c[i]-- , d[i]-- ;
}
vector< pair< ll, int >> B;
rep(i, m) B.push_back({b[i], i});
sort(rall(B));
set< pair< int , int >> ban;
rep(i, l) ban.insert({c[i], d[i]});
ll ans = 0 ;
rep(i, n) {
rep(j, m) {
if (! ban.count({i, B[j].second})) {
chmax(ans, a[i] + B[j].first);
break ;
}
}
}
cout << ans << endl;
}
Wed, Jan 29, 2025 D - Keep Distance https://atcoder.jp/contests/abc382/tasks/abc382_d
$1 \leq x \leq y \leq z \leq 6$ を満たす $(x,y,z)$ の組み合わせを求めるのと同じ要領で解ける。
\begin{align}
x^\prime &= x \nonumber \\
y^\prime &= y+1 \nonumber \\
z^\prime &= z+2 \nonumber
\end{align}
とすると $1 \leq x^\prime < y^\prime < z^\prime \leq 8$ となる。
$(x^\prime, y^\prime, z^\prime)$ が決まると $(x,y,z)$ も一通りに決まるから $(x^\prime, y^\prime, z^\prime)$ の組み合わせを調べれば良い。
やることとしては $1 \sim 8$ までの数字から重複なく3つ選び小さい方から順に $(x^\prime, y^\prime, z^\prime)$ とすれば良い。
実際の問題について考える
\begin{align}
&A_1 + 10 \leq A_2 \Rightarrow A_1 \leq A_2 - 10 < A_2 - 9 \nonumber \\
&A_2 + 10 \leq A_3 \Rightarrow A_2 - 9 \leq A_3 - 19 \Rightarrow A_2 - 9 < A_3 - 18 \nonumber \\
&\vdots \nonumber
\end{align}
Mon, Jan 27, 2025 D - Hidden Weights $u_i \rightarrow v_i$ への辺の重みは $w_i$, $v_i \rightarrow u_i$ への辺を $-w_i$ で張り適当なところを $0$ でスタートして新たに訪問したノードに重みに応じて値をおいていく。
負の重みの辺を追加するのがミソ
struct Edge {
ll to, weight;
};
void dfs (vector< vector< Edge>>& graph, vll& ans, int now) {
for (auto [to, w] : graph[now]) {
if (ans[to] != INF)
continue ;
ans[to] = ans[now] + w;
dfs(graph, ans, to);
}
}
void solve () {
int N, M;
cin >> N >> M;
vll u(M), v(M), w(M);
rep(i, M) {
cin >> u[i] >> v[i] >> w[i];
u[i]-- , v[i]-- ;
}
vector< vector< Edge>> graph(N);
rep(i, M) {
graph[u[i]].push_back({v[i], w[i]});
graph[v[i]].push_back({u[i], - w[i]});
}
vll ans(N, INF);
rep(i, N) {
if (ans[i] == INF) {
ans[i] = 0 ;
dfs(graph, ans, i);
}
}
print(ans);
}
Sun, Jan 26, 2025 A - Replace Digits https://atcoder.jp/contests/arc191/tasks/arc191_a
コンテスト中考えていたこと $T$ を降順に sort して $S$ が大きくなる限りは $T$ の文字に置き換えていく。改変後の $S$ を $S^\prime$ とする。
余った文字はすでに置き換えた文字の元々あった場所より、余った文字の元々の index が小さければその文字を捨てる(捨てる文字を先に差し込んでからあとから置き換え対象の文字で上書きできるので捨てても問題ない)。それもできなかったら余った文字の中で元々の index が一番後ろとなるものを $S^\prime$ の最後の文字に置き換えればいいと思った。
提出して WA になった。
https://atcoder.jp/contests/arc191/submissions/62131491
このケースだと $S = 912, T = 111$ というケースにおいて $S = 911$ となってしまうが $S$ の 1 のところに $T$ の 1 で置き換えることで最終的に $S = 912$ のままにできる。
よって index の考慮に加えてすでに $S$ の中にある文字も捨てて良い。これで AC した
void solve () {
int N, M;
cin >> N >> M;
string S, T;
cin >> S >> T;
vector< pair< char , int >> t;
rep(i, M) t.push_back({T[i], i});
sort(rall(t));
set< int > used;
int i = 0 , j = 0 ;
while (i < N && j < M) {
if (S[i] < t[j].first) {
S[i] = t[j].first;
used.insert(t[j].second);
i++ ;
j++ ;
} else {
i++ ;
}
}
if (j < M) {
set< char > freq;
for (char c : S)
freq.insert(c);
rep2(k, j, M) {
if (freq.count(t[k].first)) {
used.insert(t[k].second);
continue ;
}
auto it = used.lower_bound(t[k].second);
if (it != used.end()) {
continue ;
}
S[N - 1 ] = t[k].first;
used.insert(t[k].second);
}
}
cout << S << endl;
}
https://atcoder.jp/contests/arc191/submissions/62132625
Sun, Jan 26, 2025 L - Deque https://atcoder.jp/contests/dp/tasks/dp_l
結局のところ両者ともに自分の得点が最大になるように行動すればよい
まずは実験 $X = a_1, Y = 0$
$X = \max\\{ {a_1, a_2} \\}, Y = \min \\{ a_1, a_2 \\}$
$X = \max \\{a_1 + (a_2, a_3 \text{の範囲で後手のときの得点}), (a_1, a_2 \text{の範囲で後手のときの得点}) + a_3 \\}$
…
解法 $\mathrm{dp}(i,j)$ を $[i,j]$ の範囲での先手の得点の最大値とすると
$$
X = \mathrm{dp}(i,j) \\\\
Y = \left( \sum_{k=i}^{j} a_k \right) - \mathrm{dp}(i,j)
$$となる。
$\mathrm{dp}(i,j)$ の値は先頭の要素をとった場合と末尾の要素をとった場合の得点を比較して最大となる方を採用すれば良い。
先頭の要素をとったときの得点は先頭の要素の値と、$(i+1,j)$ の範囲で手番が相手だったときの自分が得られる得点の和が先手の得点となるから
$a_i + \sum_{k=i+1}^{j} a_k - \mathrm{dp}(i+1,j)$ となる。
同様にして末尾の要素をとったときの得点は $\sum_{k=i}^{j-1} a_k - \mathrm{dp}(i,j-1) + a_j$。
Sat, Jan 25, 2025 B - Geometric Sequence https://atcoder.jp/contests/abc390/tasks/abc390_b
等比数列の問題
解き方が思いつかなくてテンパった。
$\lfloor \frac{A_{i+1}}{A_i} \rfloor$ を $i = 1\sim N-1$ ですべて一致するか見ようとしたが項比が非整数のときに対応できなくてサンプルでまず落ちる。項比を double
で扱ったとしても 999999998 999999999 1000000000
のような数列のときに誤差のせいで $\frac{999999998}{999999999} == \frac{999999999}{1000000000}$ が true
を返してしまう。
有理数だったらちゃんと比較できるのだけれどもということを思いついてようやく整数演算で確かめる方法を思いついた。
$\displaystyle \frac{A_{i+1}}{A_{i}} = \frac{A_{i+2}}{A_{i+1}} \rightarrow A_{i+1}^{2} = A_{i} A_{i+2}$ が成り立っているかを確かめればいい
void solve () {
ll N;
cin >> N;
vector< double > v(N);
rep(i, N) {
cin >> v[i];
}
if (N <= 2 ) {
yesno(true);
return ;
}
rep(i, N - 2 ) {
ll a = v[i], b = v[i + 1 ], c = v[i + 2 ];
if (b * b != a * c) {
yesno(false);
return ;
}
}
yesno(true);
}
C - Paint to make a rectangle 黒マスがある行番号の最小値、最大値をそれぞれ $i_{\mathrm{ min }}, i_{\mathrm{ max }}$ とし, 黒マスのある列番号の最小値、最大値をそれぞれ $j_{\mathrm{ min }}, j_{\mathrm{ max }}$ とする。
Sat, Jan 25, 2025 https://atcoder.jp/contests/abc366
D - Cuboid Sum Query 3次元累積和の問題
https://atcoder.jp/contests/abc366/tasks/abc366_d
$f_i(a,b) = \sum_{i=a}^{b}$ とする。
\begin{align}
\sum_{i=a}^{x} &\sum_{i=b}^{y} \sum_{i=c}^{z} \nonumber = f_i(a,x)f_j(b,y)f_k(c,z) \nonumber \\
&= \left\{ f_i(1,x) - f_i(1,a-1) \right\} \nonumber \\
&~~~ \times \left\{ f_j(1,y) - f_j(1,b-1) \right\} \nonumber \\
&~~~ \times \left\{ f_k(1,z) - f_k(1,c-1) \right\} \nonumber \\
&= f_i(1,x)f_j(1,y)f_k(1,z) - f_i(1,x)f_j(1,y)f_k(1,c-1) \nonumber \\
&~~~ - f_i(1,x)f_j(1,b-1)f_k(1,z) + f_i(1,x)f_j(1,b-1)f_k(1,c-1) \nonumber \\
&~~~ - f_i(1,a-1)f_j(1,y)f_k(1,z) + f_i(1,a-1)f_j(1,y)f_k(1,c-1) \nonumber \\
&~~~ + f_i(1,a-1)f_j(1,b-1)f_k(1,z) - f_i(1,a-1)f_j(1,b-1)f_k(1,c-1) \nonumber \\
\end{align}
Sun, Jan 19, 2025 C https://atcoder.jp/contests/abc389/tasks/abc389_c
deque にヘビの頭の座標と長さを入れる。
タイプ2 で全体の要素の座標を更新すると要素数分更新する必要があるので非効率。出ていったヘビの長さを記録しておき、あとからその値を引く
タイプ1 のときは最後尾のヘビの座標が $x$, 長さが $L$ のときは $(x+L, l)$ を deque に追加する タイプ2 のとき、先頭のヘビの座標と長さが $(x,l)$ のとき $sub = x+l$ と記録しつつ、先頭のヘビを deque から抜く。deque にいるヘビの数が 0 のときは $sub = 0$ にする タイプ3 のとき $deque[k].first - sub$ を出力する void solve () {
ll Q;
cin >> Q;
ll sub = 0 ;
// head pos, length
deque< pair< ll, ll>> que;
rep(i, Q) {
ll x;
cin >> x;
if (x == 1 ) {
ll l;
cin >> l;
if (que.size()) {
auto [prevpos, prevlen] = que.back();
que.push_back({prevpos + prevlen, l});
} else {
que.push_back({0 , l});
}
} else if (x == 2 ) {
auto [pos, len] = que.front();
que.pop_front();
sub = pos + len;
if (que.empty())
sub = 0 ;
} else { // x==3
ll k;
cin >> k;
k-- ;
cout << que[k].first - sub << endl;
}
}
}
D https://atcoder.jp/contests/abc389/tasks/abc389_d
Sat, Jan 11, 2025 D - Coming of Age Celebration https://atcoder.jp/contests/abc388/tasks/abc388_d
0-index で考える。$i$ 番目の宇宙人が成人したときにもらう石の数を $C_i$ とし、 $i$ 番目の宇宙人がこれから成人する宇宙人に渡す石の数を $D_i$ とすると $D_i = \min(A_i + C_i, N - i - 1)$ である。
$i$ 番目の宇宙人が最終的に持っている石の数は $A_i + C_i - D_i$ になる。
$C_i$ の管理方法を工夫することでこの問題を解く。
$i$ 番目の宇宙人が何番目の宇宙人にまで石を配るかを考えると $i+D_i$ 番目の宇宙人にまで石を配ることになる。この数を multiset に保存していく。
$j > i$ 番目の人がもらう石の数は $ j \leq x, x \in multiset$ を満たす $x$ の個数と一致する。$j$ 番目の人のときに $x < j$ となる要素は意味がないから予め消しておくと multiset の大きさが $j$ 番目の宇宙人がもらう石の数に等しくなる。これにより $C_j$ が求まる。$C_j$ が求まれば $D_j$ も決まるので、$D_j > 0$ ならば $j + D_j$ を multiset に入れる。これにより答えが求まる。
Fri, Jan 10, 2025 D https://atcoder.jp/contests/abc254/tasks/abc254_d
命題1 $i \times j \text{ が平方数} \leftrightarrow \frac{i\times j}{f(i) \times f(j)} \text{ が平方数}$
($\rightarrow$)
$i \times j$ が平方数であるから $i \times j$ は以下のようにかける。
$$
i \times j = \prod_{k} p^{2r_k}_{k}
$$ここで、$p_k$ は素数であり、$r_k$ は非負整数。
$f(i)$, $f(j)$ も平方数であるから
$$
f(i) = \prod_{k} p^{2n_{k}}_{k}
$$$$
f(j) = \prod_{k} p^{2m_{k}}_{k}
$$とかける。ここで $n_k$, $m_k$ は非負整数。
よって
$$
\frac{i\times j}{f(i) \times f(j)} = \prod_{k} p^{2(r_k - n_k - m_k)}_k
$$であるから、因数が偶数となるため平方数。
$(\leftarrow)$
$\frac{i\times j}{f(i) \times f(j)}$ が平方数であるから、$\frac{i\times j}{f(i) \times f(j)} = x^2$ となるある正の整数 $x$ が存在する。
同様にして $f(i) = y^2, f(j) = z^2$ とかける。ここで $y, z$ は正の整数.
Sun, Dec 22, 2024 C - Doukasen 解説の解説
ぶつかる地点を $x$ とする。左端から $x$ まで行く時間を $t_l$, 右端から $x$ まで行く時間を $t_r$ とすると当たり前だが $t_l = t_r$ が成り立つ。
一方、 左端からだけ燃やしたときにすべてが燃えるのにかかる時間は $T = \sum^{N}_{i=1}\frac{A_i}{B_i}$ である。
$t = t_l$ とすると $2t = T$ であるから
$t = \frac{1}{2} \sum_{i=1}^{N}\frac{A_i}{B_i}$
である。
Sun, Apr 10, 2022 A - Move Right 問題
$0 s_0 s_1 s_2$ を出力すればよい
void solve () {
string s;
cin >> s;
cout << 0 << s.substr(0 , 3 ) << endl;
}
提出コード
B - Unique Nicknames 人 $i$ について, 次の条件 $P$, $Q$ のうちいずれかが成り立っていればその人にあだ名をつけることは可能である.
P: $s_i$ をニックネームとして選んだとき, 全ての $i \neq j$ に対して $s_i \neq s_j \wedge s_i \neq t_i$ Q: $t_i$ をニックネームとして選んだとき, 全ての $i \neq j$ に対して $s_i \neq s_j \wedge s_i \neq t_i$ これが全ての人に対して調べる.
void solve () {
int n;
cin >> n;
vector< string> S(n), T(n);
rep(i, n) cin >> S[i] >> T[i];
rep(i, n) {
int ok = 1 ;
rep(j, n) {
if (i == j)
continue ;
if (S[i] == S[j] || S[i] == T[j])
ok = 0 ;
}
if (ok)
continue ;
ok = 1 ;
rep(j, n) {
if (i == j)
continue ;
if (T[i] == S[j] || T[i] == T[j])
ok = 0 ;
}
if (! ok) {
cout << "No" << endl;
return ;
}
}
cout << "Yes" << endl;
}
提出コード
Sat, Apr 2, 2022 A - Four Points 問題
辺が $x$ 軸に平行または $y$ 軸に平行であるから, 長方形を構成する4点に関して
$x$ 座標が同じ点が2つずつ, $y$ 座標が同じ点が2つずつある.
この問題では3点が与えられるので残る1点の座標は
$x$ 座標: 3点の $x$ 座標の XOR $y$ 座標: 3点の $y$ 座標の XOR となる.
提出コード
B - Get Closer 問題
$(A, B)$ 方向への単位ベクトルを求めれば良いので $\displaystyle \left(\frac{A}{\sqrt{A^2 + B^2}}, \frac{B}{\sqrt{A^2 + B^2}} \right)$ が答え.
提出コード
C - Coupon 問題
まず各商品に対して $\frac{A_i}{X}$ 枚のクーポンを使う. クーポンが足りなくなったらそのまま.
クーポンが余った場合, 各商品に払わないといけない金額は $X$ 未満になっているので, 残高が高い順にクーポンを適用させていけばよい.
提出コード
D - 2-variable Function 問題
$f(a,b) = a^3 + a^2b + ab^2 + b^3$ とする.
$f(a,b) \geq N$ を満たす最小の $f(a, b)$ を求める.
Sat, Mar 12, 2022 ##
[問題]()
[提出コード]()
A - Shampoo 問題
単純にシミュレーションをするか, $V <= A+B+C$ のときは3人が1順するごとに $A+B+C$ 減るので
$V$ を V % (A + B + C)
スタートにしても問題ない.
この状況で誰が使うときに不足するかを調べればよい.
void solve () {
int v, a, b, c;
cin >> v >> a >> b >> c;
v %= a + b + c;
if (v - a < 0 ) {
cout << "F" << endl;
return ;
}
v -= a;
if (v - b < 0 ) {
cout << "M" << endl;
return ;
}
cout << "T" << endl;
}
提出コード
Sun, Mar 6, 2022 A - T-shirt 問題
$X \leq A$ なら 1 $X > B$ なら 0 それ以外なら $\displaystyle \frac{C}{B-A}$ を出力
提出コード
B - Minimize Ordering 問題
文字を昇順に並べ替えたものが答え
提出コード
C - 1111gal password 問題
$dp[i][x]$: $i$ 桁の整数で, $i$ 桁目が $x$ である問題の条件を満たす整数の場合の数とする.
また初期状態として $\forall x, dp[1][x] = 1$ とする.
$i$ 桁の整数で $i$ 桁目が $x$ となるような整数の場合の数は
$i-1$ 桁目の数と $x$ の差の絶対値が1以下となるような $i-1$ 桁の整数の数の合計であるから
dp[i][x] += dp[i-1][x]
if (x-1 >= 1) dp[i][x] += dp[i-1][x-1]
if (x+1 <= 9) dp[i][x] += dp[i-1][x+1]
となる.
Sun, Feb 27, 2022 A - Digit Machine 問題
a[a[a[0]]]
を出力すれば良い.
void solve () {
vint a(10 );
rep(i, 10 ) cin >> a[i];
cout << a[a[a[0 ]]] << endl;
}
提出コード
B - Pasta 問題
map にそれぞれの麺の長さの数を保存して, $i$ 日目に麺が残っていたら 1 decrement,
残っていなかったら No
を出力.
最終的に $M$ 日間の食事計画に問題なければ Yes
を出力.
提出コード
C - Connect 6 問題
$(i, j)$ を始点として上下左右斜め8方向に対して6マスの中に白の数が2つ以下となっているかを全探索する.
方向が違う以外は調べる処理の中身は同じなので関数化しておくと楽.
bool search (int i, int j, int di, int dj) {
// (i, j): start point
// (fi, fj): final point
int fi = i + di * 5 ;
int fj = j + dj * 5 ;
if (fi < 0 || fi >= n || fj < 0 || fj >= n)
return false;
int rem = 6 ;
int r = i, c = j;
int numwhite = 0 ;
rep(i, rem) {
numwhite += grid[r][c] == '.' ;
r += di;
c += dj;
}
if (numwhite <= 2 ) {
return true;
}
return false;
}
提出コード
Sun, Feb 27, 2022 A - A ↔ BB 問題
文字列のうち A
or B
が連続する区間はいったん全ての A
を BB
にして
その区間の文字を B
のみからなるようにした後に先頭から近い方の BB
を A
に変換していくのを繰り返すのが最適.
例
CABAC
-> CBBBBBC
-> CAABC
模範解答を見て知ったが string(n, 'A')
とすると A
が $n$ 文字の並んだ文字列を生成できるらしい.
またどうやら C++ の string で str1 += str2
とするのは str2
の長さ分を加える計算量しか掛からないっぽい.
str1 += str2
は str1
に push_back
で str2
の要素を1個ずつ入れるのと計算量としては変わらないっぽい.
string string_cancat (int n) {
string s = "" ;
rep(i, n) s += ('a' + (i % 5 ));
return s;
}
void test (int n) {
clock_t t = clock();
string s = string_cancat(n);
double time = (double )(clock() - t) / CLOCKS_PER_SEC;
printf("n = %10d: \t time = %.9lf sec \n " , n, time);
}
void solve () {
int n = 10 ;
while (n < 100000000 ) {
test(n);
n *= 10 ;
}
}
n = 10: time = 0.000002000 sec
n = 100: time = 0.000009000 sec
n = 1000: time = 0.000013000 sec
n = 10000: time = 0.000090000 sec
n = 100000: time = 0.000807000 sec
n = 1000000: time = 0.012396000 sec
n = 10000000: time = 0.049614000 sec
提出コード
Sun, Feb 20, 2022 A - Edge Checker 問題
$|a-b| = 1$ または $a = 1, b = 10$ のときは Yes
, それ以外のときは No
提出コード
B - Count Distinct Integers 問題
$N$ 個の要素全部 set
に突っ込んで最後に size を出力
提出コード
C - Jumping Takahashi 問題
$dp[i][x]$ を $i$ 回目のジャンプで座標 $x$ に行けるかとし
初期値 $dp[0][0] = true$ とする.
$dp[i-1][x] = true$ ならば $dp[i][x+a] = dp[i][x+b] = true$ に更新する.
提出コード
D - Strange Balls 問題
筒の中に入っているボールの数を同じ数字が書かれたボールに関しては圧縮してもつ.
筒の中に [2, 3, 3, 2, 4, 4, 4, 5]
と入っていたら
[{2, 1}, {3, 2}, {2, 1}, {4, 3}, {5, 1}]
という風に {数字, 個数}
として保持する.
Sat, Feb 19, 2022 A - Horizon 問題
問題分通りの値を出力するだけ.
提出コード
B - Integer Division 問題
$X > 0$ のときは素直に整数除算 $X / 10$ を出力する.
$X < 0$ のとき, $X \equiv 0 \mod 10$ なら整数除算 $X / 10$ を出力し,
$X % 10 \neq 0$ なら $X / 10 - 1$ を出力する.
void solve () {
ll x;
cin >> x;
if (x < 0 && x % 10 != 0 ) {
cout << x / 10 - 1 << endl;
} else {
cout << x / 10 << endl;
}
}
余談
Sun, Feb 13, 2022 A - Floor, Ceil - Decomposition 数字 $x$ をそのままにするか, $\lfloor \frac{x}{2} \rfloor$, $\lceil \frac{x}{2} \rceil$ に書き換えるかは
$x < \lfloor \frac{x}{2} \rfloor \times \lceil \frac{x}{2} \rceil$ となれば書き換えたほうが全体の積が大きくなり,
その他の場合は $x$ のままにするのがよい.
おおよそ $\displaystyle \left\lfloor \frac{x}{2} \right\rfloor \times \left\lceil \frac{x}{2} \right\rceil \approx \frac{x^2}{4}$ となることを考え,
$y = x$ と $y = \frac{x^2}{4}$ のグラフを描くと $0 \leq x \leq 4$ の区間では $y = x$ が,
$x > 4$ の区間では $y = x^2 / 4$ のほうが大きいことがわかる.
$g(x) = \max(x, \left\lfloor \frac{x}{2} \right\rfloor \times \left\lceil \frac{x}{2} \right\rceil)$ とすると
Tue, Feb 1, 2022 C - kasaka 先頭にある連続する a
の数を $x$, 末尾に連続する a
の数を $y$ とすると
$x > y$ のとき, どうやっても回文にはできない.
それ以外のとき, 先頭と末尾の a
を消して残った文字が回文になっていれば Yes
, そうでなければ No
.
初め以下のようなコードを書き AC を取れてしまったが, これは嘘解法.
void solve () {
string s;
cin >> s;
int l = 0 , r = (int )s.size() - 1 ;
while (l < r) {
// 頭に a を追加する
if (s[l] != 'a' && s[r] == 'a' ) {
r-- ;
} else if (s[l] == s[r]) {
l++ ;
r-- ;
} else {
cout << "No" << endl;
return ;
}
}
cout << "Yes" << endl;
return ;
}
axyaxa
という文字列のとき上のプログラムだと Yes
を出力するが, 本来は No
を出力すべき
Tue, Feb 1, 2022 A - Exponential or Quadratic 問題
愚直に $2^n$ を計算すると $n$ が最大で $10^9$ のためオーバーフローする.
$n$ が十分大きいところでは $2^n > n^2$ となるため, そのようなところを探す.
$n = 1$: $2^n = 2 > 1 = n^2$ $n = 2$: $2^n = 4 = 4 = n^2$ $n = 3$: $2^n = 8 < 9 = n^2$ $n = 4$: $2^n = 16 = 16 = n^2$ $n = 5$: $2^n = 32 > 25 = n^2$ $n = 6$: $2^n = 64 > 36 = n^2$ $\vdots$ $n = 2, 3, 4$ のときは題意は false であるがそれ以外では true となる.
Sat, Jan 29, 2022 A - Bridge and Sheets すでに敷いたシートの右端を $r$ とすると, $r < x$ となる $x$ までをカバーするのに必要なシーツの枚数は
$\displaystyle \left\lceil \frac{x-r}{W} \right\rceil$ である.
これをひたすら繰り返す
提出コード
B - Reserve or Reverse $l = 1, r = N$ として以下を繰り返すと辞書順最小になる
$[l,r]$ の範囲にある文字のうち $s_l$ 未満の最小の文字のうちで一番右側のあるものの index を $r^\prime$ とすると $s_l$ と $s_{r^\prime}$ を交換し $r = r^\prime - 1$ に更新する. そのようなものがない場合は $l$ をインクリメントする $l \geq r$ となったら終了する. void solve () {
int n;
cin >> n;
string s;
cin >> s;
vvint a(26 );
rep(i, n) {
a[s[i] - 'a' ].push_back(i);
}
int l = 0 , r = n - 1 ;
while (l < r) {
// [l,r] の範囲にある文字のうち s[l] 未満の最小の文字のうちで一番右側にあるものの index を探す
int ok = 0 ;
rep(c, 26 ) {
if (s[l] - 'a' <= c)
break ;
// 右端以上になっている要素は消す
while (! a[c].empty() && a[c].back() > r)
a[c].pop_back();
// 一つも要素がない or 左端より小さい値しかなかったら交換できないので飛ばす
if (a[c].empty() || a[c].back() < l)
continue ;
r = a[c].back();
ok = 1 ;
break ;
}
if (ok) {
swap(s[l], s[r]);
r-- ;
}
l++ ;
}
cout << s << endl;
}
$r^\prime$ の探し方を二分探索で探すという方法もある.
Sun, Jan 23, 2022 B - Who is missing? 問題
出現回数が3回の数字を出力すればよい.
愚直に解くと以下のようになる. これで AC は一応取れる.
void solve () {
int n;
cin >> n;
vint card(n + 1 );
rep(i, 4 * n - 1 ) {
int a;
cin >> a;
card[a]++ ;
}
rep2(i, 1 , n + 1 ) {
if (card[i] != 4 )
cout << i << endl;
}
}
別解
数字 $x$ を XOR で偶数回作用させると 0 になり, 奇数回作用させると $x$ になることを思い出すと,
出力すべき数字だけ奇数回だけ出現するので, $XOR(A_1, A_2, \cdots, A_{4N-1})$ が答えとなる.
void solve () {
int n;
cin >> n;
int x = 0 ;
rep(i, 4 * n - 1 ) {
int y;
cin >> y;
x ^= y;
}
cout << x << endl;
}
tourist の提出コード を見てこの解放を知った.
Sun, Jan 23, 2022 A - Erase by Value 問題
$A_i > A_{i+1}$ となる最初の $A_i$ を消したときが辞書順最小となる.
$A_i < A_{i+1}$ となる $A_i$ を消した場合, 辞書順であとにくる数列に変化してしまうので数列が広義単調増加しているうちはその文字は消すべきではない.
B - Dividing Subsequence 問題
LCS の亜種. 「$b_i$ は $a_i$ の倍数」という条件を $a_i = b_i$ に変えると LCS と同じになる.
ただし, LCS を使うと計算量が $O(N^2)$ になってしまうので, そのままでは適用することができない.
$Q_j$ が $P_i$ の倍数となるような index のペア $(i,j)$ に関して,
第1要素で昇順, 第2要素で降順に並べたものに対して, 第2要素の LIS の長さを求めると問題の答えになる.
入力例を使って実際に考えてみる.
3 1 4 2
4 2 1 3
このとき, ペアを辺と捉えると下図のようになる.
ここで, 辺を交差しないようにできるだけ多く選択したい.
第1要素を横軸, 第2要素を縦軸に辺をプロットすると下図のようになる.
これより, 2辺 $(a_i, b_i)$, $(a_j, b_j)$ が交差しないための条件は $a_i < a_j \wedge b_i < b_j$ が成り立つことである.
この条件を満たすように辺を選んだときの, 選べる辺の最大値は先に述べたように第2要素の LIS の長さとなる.
Sun, Jan 16, 2022 D - Multiply and Rotate 問題
操作1: $x$ を $a$ 倍する 操作2: $x$ を文字列とみなして, 末尾の数字を先頭に移動させる
とする. DFS or BFS を使って, 1 からスタートして N になるまでの操作回数の最小値を求める. $N$ からスタートして $1$ にする方でも可.
DFS の計算量の正確な見積もり方はわからないが, $\log_2(10^6) = 19.93...$ であるので, 7桁の数を $a$ で割れる回数は高々20回程度で
操作2をしたところでそんなにパターン数は多くなさそうに思えるが, 本当にそうなのか微妙なところ.
際どいテストケース持ってこられたときに TLE にならないか自信がない.
E - MST + 1 問題
クエリを先読みする問題.
元々のグラフの辺と, クエリによる辺を合わせて最小全域木を作ることを考える.
作っている際にクエリ由来の辺を使ったほうが得なときは, そのクエリを Yes, そうでない場合は No を出力する.
プリム法だとグラフの構築が必要になるので, クラスカル法で解くほうが楽そう.
Mon, Jan 10, 2022 問題
$dp[i][x]$: $i$ 番目までの数字を使う, かつ, $A_i$ を $x~(x = + \text{ or } -)$ とするような式の合計とする.
具体的に書いてみるとわかりやすいが, 遷移は以下のようになる.
\begin{align}
dp[i][+] &= (dp[i-1][+] + dp[i-1][-]) + (i-1 \text{ までを使ったときの式の場合の数}) \times A_i \
dp[i][-] &= dp[i-1][+] - (A_{i-1} \text{を足したときの式の場合の数}) \times A_i
\end{align}
掛け合わせる数はフィボナッチ数列になっている. $DP$ テーブル一つだけで処理するのは面倒(pair を使って合計と個数を記録していればできる)なので掛ける数は別にテーブルに保持しておくと楽
void solve () {
int n;
cin >> n;
vector< ll> a(n + 1 );
rep(i, n) cin >> a[i + 1 ];
vector< vector< mint>> dp(n + 1 , vector< mint> (2 , 0 )), mul(n + 1 , vector< mint> (2 , 0 ));
dp[1 ][0 ] = a[1 ];
mul[1 ][0 ] = 1 ;
rep2(i, 2 , n + 1 ) {
mul[i][0 ] = mul[i - 1 ][0 ] + mul[i - 1 ][1 ];
mul[i][1 ] = mul[i - 1 ][0 ];
dp[i][0 ] = dp[i - 1 ][0 ] + dp[i - 1 ][1 ] + mul[i][0 ] * a[i];
dp[i][1 ] = dp[i - 1 ][0 ] - mul[i][1 ] * a[i];
}
cout << (dp[n][0 ] + dp[n][1 ]).val() << endl;
}
提出コード
Sun, Jan 9, 2022 A - Gold and Silver 問題
$i$ 日目に金を銀に, $j$ 日目に銀を金に交換すると金の量は $A_i / A_j$ 倍になる.
ここで
$$
\frac{A_i}{A_j} = \frac{A_i}{A_{i+1}} \times \frac{A_{i+1}}{A_{i+2}} \times \cdots \times \frac{A_{j-1}}{A_j}
$$であるから $i$ 日目に金を銀に, $j$ 日目に銀を金に交換することは
$A_i / A_{i+1}$$i$ 日目に金を銀に交換 $i+1$ 日目に銀を金に交換 $A_{i+1} / A_{i+2}$$i+1$ 日目に金を銀に交換 $i+2$ 日目に銀を金に交換 … $A_{j-1} / A_j$$j-1$ 日目に金を銀に交換 $j$ 日目に銀を金に交換 することと等価である. 金の量を増やすには $A_i > A_{i+1}$ となるところで交換を行えばいい.
void solve () {
int n;
cin >> n;
vector< int > A(n), change(n, 0 );
rep(i, n) cin >> A[i];
rep(i, n - 1 ) {
if (A[i] > A[i + 1 ]) {
change[i] ^= 1 ;
change[i + 1 ] ^= 1 ;
}
}
print(change);
}
XOR をとっているのは同じ日に金を銀に, 銀を金にという交換を連続する操作があった場合に
打ち消し合わせるため.
Sun, Jan 9, 2022 B - Range Point Distance 問題
$dist(l, r, x) = \max(0, l-x, x-r)$ であるから
\begin{align}
\max(dist(L_1, R_1, x)&, \cdots, dist(L_k, R_k, x)) \
&= \max(0, L_1 - x, x - R_1, \cdots, L_k - x, x - R_k) \
&= \max(0, \max(L_1 - x, \cdots, L_k - x), \max(x - R_1, \cdots, x - R_k))
\end{align}
である.
\begin{align}
&\max(L_1 - x, \cdots, L_k - x) = \max(L_1, \cdots, L_k) - x = A_k - x\
&\max(x - R_1, \cdots, x - R_k) = x + \max(-R_1, \cdots, -R_k) = x - \min(R_1, \cdots, R_k) = x - B_k
\end{align}
where $A_k = \max(L_1, \cdots, L_k)$ and $B_k = \min(R_1, \cdots, R_k)$.
Sat, Jan 8, 2022 C - Happy New Year 問題
$K$ を2進数にしたときの 1 となっているところを 2 に置き換えたものが答え
void solve () {
ll k;
cin >> k;
// n: bit が立っている最も大きい idx
int n = 0 ;
rep(i, 60 ) if ((k >> i) & 1 ) n = i;
string ans = "" ;
rep(i, n + 1 ) ans.push_back((k >> i) & 1 ? '2' : '0' );
reverse(all(ans));
cout << ans << endl;
}
提出コード
D - Prefix K-th Max $S = \{P_1, \cdots, P_K\}$ となる集合を考える.
$K+1$ 番目以降の数は保存しておく必要がないので, $P_{K+1} > \min(S)$ ならば $\min(S)$ を取り除き, $P_{K+1}$ を追加.
$P_{K+1} < \min(S)$ ならば $P_{K+1}$ は集合に入れずそのまま捨てる.
以降 $P_N$ まで同様の操作を行って, その時その時の $\min(S)$ を出力する.
Tue, Jan 4, 2022 D - Between Two Arrays 問題
$dp[i][x]$: $i$ 番目までの数が決まっており, $c_i = x$ のときの場合の数とする.
$c_i = x$ となるときの場合の数は, $c_{i-1} \leq x$ となる場合の数であるから
$\displaystyle dp[i][x] = \sum_{k = a_{i-1}}^x dp[i-1][k]$
これを元に naive に実装すると以下のようになる.
vector< vector< ll>> dp(n, vector< ll> (MAX, 0 ));
rep2(x, a[0 ], b[0 ] + 1 ) dp[0 ][x] = 1 ;
rep2(i, 1 , n) {
rep2(x, a[i], b[i] + 1 ) {
rep2(k, a[i - 1 ], x + 1 ) {
dp[i][x] += dp[i - 1 ][k];
dp[i][x] %= MOD;
}
}
}
ll ans = 0 ;
rep(i, MAX) {
ans += dp[n - 1 ][i];
ans %= MOD;
}
cout << ans << endl;
しかしこれでは最大で $O(N \times 3000^2) = O(3000^3)$ の計算量がかかり制限時間に待ち合わない.
TLE 提出コード
Mon, Dec 27, 2021 C - Product 問題
$\displaystyle \prod^N_{i=1}L_i \leq 10^5$ より, 考えうるボールの取り出し方を全通り試しても $O(10^5)$ なので全探索する.
$1 \leq a_{i,j} \leq 10^9$ と $N \geq 2$ より, $10^9$ という数が3回以上掛けることがありうるが, 倍精度整数で表現できる整数の範囲はたかだか18桁なので愚直に積を取ると途中でオーバーフローしてしまう.
掛け算をしている途中で $X < \text{(それまでの積)} \times a_{i,j}$ としたいがこのままだと右辺がオーバーフローする可能性があるので, $X / \text{(それまでの積)} < a_{i,j}$ とする.
void dfs(int depth, ll prod) {
if (depth == N) {
if (prod == X) ans++;
return;
}
for (ll x : a[depth]) {
// X < x*prod
if (X / prod < x) continue;
dfs(depth+1, prod*x);
}
}
このオーバーフロー対策を入れないと
Mon, Dec 27, 2021 A - Permutation Grid 問題
1-index で考え, grid の $A$ と呼ぶことにする.
公式解説 の通り, $R$, $C$ の並び方がともに $1, 2, \cdots, n$ である場合は, 上から $r$ 行目, $c$ 行目のマスが $r+c \geq n + 1$ である場合に黒で, それ以外では白となる.
ここで行 $i$, $j$ を入れ替えたとき $C_1, \cdots, C_n$ の結果に影響を与えない.
同様に列 $i$, $j$ を入れ替えたとき $R_1, \cdots, R_n$ の結果に影響を与えない.
$R_i$, $C_i$ が昇順になるように行・列の入れ替えを行ってできる grid を $A^\prime$ としたとき $A$ の $(r, c)$ 成分が $A^\prime$ のどの成分に対応するか調べることでマスが黒か白か判定できる.
$R$, $C$ は $1, \cdots, n$ の順列だから, ソートによって $r$ 行目は $R_r$ 行目に, $c$ 列目は $C_c$ 列目に移動する.
よって $R_r + C_c \geq n+1$ のとき黒, それ以外のときは白となる.
Thu, Dec 23, 2021 A - QQ solver 問題
char で受けて '0'
で引くということもできるが, iostream は条件に合うものを最大長マッチでとってくる(らしい)ので下のような書き方でも通る.
int a, b;
char x;
cin >> a >> x >> b;
cout << a*b << endl;
提出コード
C - Graph Isomorphism 問題
高橋くんの番号と青木くんの番号の対応関係 $N!$ 通りをすべて試して, グラフが一致すれば Yes
, だめなら No
.
提出コード
D - Weak Takahashi 問題
$(1, 1)$ から BFS してステップ数が最大となるものが答え
提出コード
E - Rook Path 問題
$H \times W$ のグリッドを下のような領域に分ける.
$S_{0,0} = \{(x, y) | x \neq x_2 \wedge y \neq y_2 \}$ (白の領域) $S_{0,1} = \{(x, y_2) | x \neq x_2\}$ (赤の領域) $S_{1,0} = \{(x_2, y) | y \neq y_2\}$ (青の領域) $S_{1,1} = \{(x_2, y_2)\}$ (緑の領域)
Sun, Dec 12, 2021 C - Counting 2 昇順でソートして lower_bound 使って $x_i$ 以上となる index を調べる.
二部探索用途で multiset を使うと遅いので注意.
ref: multiset vs vector
submission code
D - Neighbors 人を node, 隣り合う人の情報を edge としグラフを構築する.
node の次数が3以上のとき, またはグラフに cycle がある場合は No
, それ以外は Yes
を出力する.
submission code: cycle 検出方法ごとの実装
Union Find 辺を作る前に両端の node が同じ親を持っているか確認する. 同じだった場合, 辺を張ると cycle を作ってしまうことがわかる. 1
|
2 -- 3
1-2, 2-3 の辺を張った状態で 1, 3 のそれぞれの親を調べると同じになる. その場合, 1-3 に辺を張ると cycle ができることがわかる.
Sun, Dec 12, 2021 int <-> string to_string(int)
-> stringto_string(ll)
-> stringstoi(string)
-> intstoll(string)
-> llpriority queue 昇順
#include <iostream>
#include <queue>
int main () {
// intを要素に持つ優先順位付きキュー.
// 降順に処理する
std:: priority_queue< int > que;
// データを追加する
que.push(3 );
que.push(1 );
que.push(4 );
// 処理順に出力する
while (! que.empty()) {
std:: cout << que.top() << std:: endl;
que.pop();
}
}
4
3
1
降順に出力
#include <iostream>
#include <queue>
int main () {
// intを要素に持つ優先順位付きキュー.
// 降順に処理する
std:: priority_queue< int , vector< int > , greater< int >> que;
// データを追加する
que.push(3 );
que.push(1 );
que.push(4 );
// 降順順に出力する
while (! que.empty()) {
std:: cout << que.top() << std:: endl;
que.pop();
}
}
1
3
4
struct で priority queue
Sun, Dec 12, 2021 入出力の高速化 他人のコードをみてたまにやっているのを見かける以下のような書き方は入出力を高速化するためにやっているらしい
ios_base:: sync_with_stdio(false);
cin.tie(nullptr );
cout << val << '\n' ;
ref: https://maspypy.com/library-checker-many-a-b#google_vignette
multiset vs vector multiset は遅いので二部探索のように使うときは注意する.
insert, find, erase の計算量は $\log(N)$ ($N$ は要素数)であるが, 定数項が効いているのかなかなかに遅い.
#include <bits/stdc++.h>
#define all(v) (v).begin(), (v).end()
#define rall(v) (v).rbegin(), (v).rend()
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define rep2(i, k, n) for (int i = (k); i < (n); ++i)
using namespace std;
using ll = long long ;
using P = pair< int , int > ;
// using P = pair<ll,ll>;
const ll INF = (ll)1e18 ;
// const int INF = (int)1e9+7;
template < typename T>
void chmin(T& a, T b) {
a = min(a, b);
}
template < typename T>
void chmax(T& a, T b) {
a = max(a, b);
}
void solve () {
int ns[] = {100 , 1000 , 10000 , 100000 , 1000000 , 10000000 };
cout << "multiset" << endl;
cout << "size \t elapsed time (sec)" << endl;
for (int n : ns) {
multiset< int > ms;
clock_t start = clock();
rep(i, n) ms.insert(i);
clock_t end = clock();
cout << n << " \t " << (double )(end - start) / CLOCKS_PER_SEC << endl;
}
cout << endl;
cout << "vector" << endl;
cout << "size \t elapsed time (sec)" << endl;
for (int n : ns) {
vector< int > v(n);
clock_t start = clock();
rep(i, n) v[i] = i;
sort(all(v));
clock_t end = clock();
cout << n << " \t " << (double )(end - start) / CLOCKS_PER_SEC << endl;
}
}
int main () {
solve();
return 0 ;
}
multiset
size elapsed time (sec)
100 9e-06
1000 6.4e-05
10000 0.000915
100000 0.011328
1000000 0.144224
10000000 2.5446
vector
size elapsed time (sec)
100 5e-06
1000 1e-05
10000 7.5e-05
100000 0.000858
1000000 0.010139
10000000 0.130072
Sat, Dec 4, 2021 E - Fraction Floor Sum $\displaystyle \sum^{n}_{i=1} \floor{N/i}$ を求める問題.
言い換えると $y \le n / x$, $(1 \le x \le n)$ の範囲にある格子点の数を数える問題.
$$
\sum_{i=1}^{n} \floor{N/i} = 2 \sum_{i=1}^{\floor{\sqrt{n}}} \floor{N/i} - (\floor{\sqrt{n}})^2
$$計算量は $O(\sqrt{n})$ で解ける.
解説 $y = n/x$ は $y = x$ に関して対称であり, $y = n/x$ と $y = x$ は $(x,y) = (\sqrt{n}, \sqrt{n})$ で交わる.
対称性より図の青い斜線の領域に含まれる格子点の数 ($\sum^{\floor{\sqrt{n}}}_{i = 1}\floor{N/i}$) と, 赤い斜線の領域に含まれる格子点の数は一致する. 2つの領域で共通する部分に含まれる格子点の数は $(\floor{\sqrt{n}})^2$.
したがって,
$$
\begin{aligned}
(\mathrm{題意の格子点数}) \\\\
=& (\mathrm{青斜線に含まれる格子点数}) + (\mathrm{赤斜線に含まれる格子点数}) - (共通する領域に含まれる格子点数) \\\\
=& 2\sum^{\floor{\sqrt{n}}}_{i = 1}\floor{ N/i } - (\floor{ \sqrt{n} })^2
\end{aligned}
$$
Mon, Jan 1, 0001