Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
問題: Loneliness (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
thirofoo authored Aug 6, 2024
1 parent 728fa81 commit dfe40eb
Show file tree
Hide file tree
Showing 55 changed files with 2,074,067 additions and 0 deletions.
9 changes: 9 additions & 0 deletions loneliness/PROBLEM
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8; mode: python -*-

pid='loneliness'

problem(
time_limit=2.0,
id=pid,
title="Loneliness",
)
138 changes: 138 additions & 0 deletions loneliness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Loneliness

実行時間制限:2 sec

## 問題文

数直線上に人がいます。\
それぞれの人は $1, 2, ... , 60$ のクラスのうち 1 つに属しています。\
同じクラスの人は 2 人で 1 つのペアを組むことができます。<br>

$Q$ 個のクエリが与えられるので順に処理してください。各クエリは次の
2 種類のどちらかです。

- `1 L R l r`:区間 $\lbrack L, R \rparen$ 内にクラス $l, l+1, ... , r$ の人がそれぞれ奇数人、それ以外のクラスの人は偶数人いるという情報が与えられる。
- `2 L R`:区間 $\lbrack L, R \rparen$ にいる人で可能な限りペアを組んだ場合に、ペアになれずに残る人数を出力する。質問の時点で答えが一通りに定まらない場合は、代わりに `Ambiguous` と出力する。

なお、各人は複数の区間にまたがることはないものとします。

## 制約

- $1 \leq Q \leq 2 \times 10^5$
- $1 \leq L < R \leq 10^9$
- $1 \leq l \leq r \leq 60$
- 入力は全て整数である。
- 矛盾するような入力は与えられない。
- `2 L R` のクエリは少なくとも 1 回以上与えられる。

## 部分点

この問題はいくつかの小課題に分けられ、その小課題の全てのテストケースに正解した場合に、「この小課題に正解した」とみなされます。
提出したソースコードの得点は、正解した小課題の点数の合計となります。

1. (20 点) $Q \leq 3000$ を満たす。
2. (80 点) 追加の制約はない。

## 入力

入力は以下の形式で与えられます。

```txt
Q
Query_1
Query_2
:
Query_Q
```

## 出力

`2 L R` のクエリに対する答えを、1 行に 1 つずつ、順に出力せよ。

### 入力例 1

```txt
7
1 1 3 2 2
2 1 3
1 1 2 1 1
2 1 4
2 2 3
1 2 4 1 2
2 3 4
```

### 出力例 1

```txt
1
Ambiguous
2
0
```

1 つ目のクエリでは、区間 $\lbrack 1, 3 \rparen$ にはクラス 2 の人が奇数人、それ以外のクラス 1, 3 の人が偶数人いるという情報が得られます。<br>
3 つ目のクエリでは、区間 $\lbrack 1, 2 \rparen$ にはクラス 1 の人が奇数人、それ以外のクラス 2, 3 の人が偶数人存在するという情報が得られます。<br>
5 つ目のクエリでは、区間 $\lbrack 2, 3 \rparen$ にクラス 1, 2, 3 の人はそれぞれ奇数, 奇数, 偶数人存在することが分かるので、ペアになれずに残る人は 2 人です。

なお、この入力例は小課題 1, 2 の制約を満たします。

### 入力例 2

```txt
9
2 1 2
1 1 3 1 60
1 1 2 1 30
1 2 3 31 60
1 2 3 31 60
2 1 2
2 1 2
2 1 3
2 2 3
```

### 出力例 2

```txt
Ambiguous
30
30
60
30
```

1 つ目のクエリでは、区間 $\lbrack 1, 2 \rparen$ の人に関する情報がなく、答えが一通りに定まらないため `Ambiguous` と出力します。<br>
同じクエリが何度も出現する場合もあるので注意してください。<br>
なお、この入力例は小課題 1, 2 の制約を満たします。

## 解法

- `sol-cpp-partial1`: C++ 部分点 1
- `sol-py-partial1`: Python 部分点 1
- `sol-cpp-ac`: C++ AC
- `sol-py-ac`: Python AC

### 部分点 1 解法

まず、今回はクエリで現れる区間の長さは特に考慮しないため、クエリを先読みして座標圧縮をしても問題ありません。<br>

また、ある区間 $\lbrack L , R \rparen$ において、各クラスでペアを作れずに余る人は 0 or 1 人しかいません。即ち、答えはある区間 $\lbrack L , R \rparen$ における各クラスの人数の mod 2 の総和に等しいです。<br>

ここで $B_{L,R}$ 以下を定義します。

$$B_{L,R} = \sum_{i=1}^{60} 2^{i-1}\times (区間 \ \lbrack L , R \rparen \ にいるクラス \ i \ の人数 \ \text{mod 2})$$

これは、 $(B_{L,R} \land (1 \ll i)) \neq 0$ の時、区間 $\lbrack L , R \rparen$ にクラス $i$ の人が奇数人存在することを意味する値です。

$L < M < R$ において、 $B_{L,M} \oplus B_{M,R}$ を考えると、これは桁毎に見ると mod 2 の和に等しいため、二つの区間の情報を統合させたものとして相応しく $B_{L,R}$ に等しいです。

これにより、クエリ 1 の $L, R$ 間にコスト $B_{L,R}$ の辺を張ったグラフを考え、クエリ 2 における $L$ を始点として移動時にコストで XOR を取るような BFS を行うことで、$R$ に到着した時のコストの pop count がクエリ 2 の答えになります。<br>

クエリは $Q$ 個なので、このグラフにおける頂点・辺の数は高々 $2Q$ 個であるため、 $O(Q^2)$ で解くことができます。

### 満点解法

クエリ 1 で与えられる情報の端点を移動して $L$ から $R$ にたどり着けるとき、経路に依らず $B_{L,R}$ が一意に定まることがわかります。<br>
よって、これはポテンシャルの概念と同じくとらえることができ、重み付き UnionFind の重みとして $B_{L,R}$ を管理することで $O(Q)$ で解くことができます。
3 changes: 3 additions & 0 deletions loneliness/sol-cpp-ac/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
cxx_solution(src='main.cc', flags=["-std=c++2a", "-O2"])
expected_score(100)
90 changes: 90 additions & 0 deletions loneliness/sol-cpp-ac/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
struct weighted_dsu {
public:
weighted_dsu(): _n(0) {}
explicit weighted_dsu(ll n): _n(n), parent_or_size(n, -1), diff_weight(n, 0) {}

ll merge(ll a, ll b, ll w) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);

w ^= weight(a);
w ^= weight(b);

ll x = leader(a), y = leader(b);
if(x == y) return x;
if(-parent_or_size[x] < -parent_or_size[y]) swap(x, y);

parent_or_size[x] += parent_or_size[y];
parent_or_size[y] = x;
diff_weight[y] = w;

return x;
}

bool same(ll a, ll b) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);
return leader(a) == leader(b);
}

ll leader(ll a) {
assert(0 <= a && a < _n);
if(parent_or_size[a] < 0) return a;
ll r = leader(parent_or_size[a]);
diff_weight[a] ^= diff_weight[parent_or_size[a]];
return parent_or_size[a] = r;
}

ll weight(ll x) {
leader(x); // 経路圧縮
return diff_weight[x];
}

ll diff(ll x, ll y) {
return weight(y) ^ weight(x);
}

private:
ll _n;
vector<ll> parent_or_size;
vector<ll> diff_weight;
};

int main() {
cin.tie(nullptr);
ios_base::sync_with_stdio(false);

int q;
cin >> q;
vector<int> comp;
vector<tuple<int, int, int, int, int>> query;
for(int i = 0; i < q; i++) {
int t, L, R, l = -1, r = -1;
cin >> t >> L >> R;
comp.emplace_back(L);
comp.emplace_back(R);
if(t == 1) cin >> l >> r;
query.emplace_back(tuple(t, L, R, l, r));
}
sort(comp.begin(), comp.end());
comp.erase(unique(comp.begin(), comp.end()), comp.end());
for(auto& [t, L, R, l, r]: query) {
L = lower_bound(comp.begin(), comp.end(), L) - comp.begin();
R = lower_bound(comp.begin(), comp.end(), R) - comp.begin();
}

weighted_dsu uf(comp.size());
for(auto& [t, L, R, l, r]: query) {
if(t == 1) uf.merge(L, R, (1LL << r) - (1LL << (l - 1)));
else {
if(!uf.same(L, R)) cout << "Ambiguous\n";
else cout << __builtin_popcountll(uf.diff(L, R)) << '\n';
}
}

return 0;
}
3 changes: 3 additions & 0 deletions loneliness/sol-cpp-partial1/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
cxx_solution(src='main.cc', flags=["-std=c++2a", "-O2"], challenge_cases=["*_all.in"])
expected_score(10)
61 changes: 61 additions & 0 deletions loneliness/sol-cpp-partial1/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

int main() {
cin.tie(nullptr);
ios_base::sync_with_stdio(false);

int q;
cin >> q;
vector<int> comp;
vector<tuple<int, int, int, int, int>> query;
for(int i = 0; i < q; i++) {
int t, L, R, l = -1, r = -1;
cin >> t >> L >> R;
comp.emplace_back(L);
comp.emplace_back(R);
if(t == 1) cin >> l >> r;
query.emplace_back(tuple(t, L, R, l, r));
}
sort(comp.begin(), comp.end());
comp.erase(unique(comp.begin(), comp.end()), comp.end());
for(auto& [t, L, R, l, r]: query) {
L = lower_bound(comp.begin(), comp.end(), L) - comp.begin();
R = lower_bound(comp.begin(), comp.end(), R) - comp.begin();
}

vector<vector<tuple<int, int, int>>> Graph(comp.size());
for(auto& [t, L, R, l, r]: query) {
if(t == 1) {
Graph[L].emplace_back(R, l, r);
Graph[R].emplace_back(L, l, r);
} else {
// 愚直に Graph 上を探索
vector<bool> visited(comp.size(), false);
queue<pair<int, ll>> que;
que.push({L, 0});

bool arrived = false;
while(!que.empty()) {
auto [now, bit] = que.front();
que.pop();
if(now == R) {
cout << __builtin_popcountll(bit) << '\n';
arrived = true;
break;
}
if(visited[now]) continue;
visited[now] = true;
for(auto [to, nl, nr]: Graph[now]) {
if(visited[to]) continue;
que.push({to, bit ^ ((1LL << nr) - (1LL << (nl - 1)))});
}
}
if(!arrived) cout << "Ambiguous\n";
}
}

return 0;
}
3 changes: 3 additions & 0 deletions loneliness/sol-cpy-ac/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
script_solution(src='main.py')
expected_score(100)
Loading

0 comments on commit dfe40eb

Please sign in to comment.