Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Forget the circle for a second. For a fixed prefix, make a graph whose vertices are chords, and put a directed edge for when chords and intersect. A chain is exactly a directed path in this DAG, including paths of length .
Only parity matters. Let be the parity of chains ending at , and be the parity of chains starting at inside the current prefix. Then
The number of chains containing chord has parity . The trap: after adding a new chord, old values do not just toggle for chords directly intersecting it. They toggle for every old chord that has an odd number of chains leading into the new chord. Direct-only logic gets cooked.
Let be the parity of chains starting at and ending at . Then , , and the bad-set vector is So maintain the column values .
When chord is inserted, only the new column appears. If means chord intersects , then Both and are interval queries, because intersecting a chord means exactly one endpoint lies inside its interval.
We only care about parity. That is the first big simplification: all counts live in , so even/odd becomes XOR arithmetic.
Turn chains into paths
For a fixed prefix , make a DAG:
A chain is exactly a directed path in this DAG. Singletons are paths of length .
Let
Then , and for .
Also define
as the parity of chains ending at , and
as the parity of chains starting at .
A chain containing chord uniquely splits into a chain ending at and a chain starting at . So the parity of chains containing is
Therefore the prefix is tight-knit iff there is no with and .
The direct-toggle trap
When a new chord is appended, is easy:
Old values never change.
But old values are not toggled only for chords that directly intersect . That is fake-solution bait. An old chord gets new chains ending at through every path from into some chord that intersects .
If when old chord intersects , then the new contribution to is
So we need to apply this transitive update without touching all chords.
Column vectors
For the proof, imagine every chord has a formal basis vector . Define the bad-set vector
The prefix is tight-knit iff .
Using ,
=\sum_i L_i\left(\sum_jP_{i,j}\right)e_i =\sum_j\left(\sum_i L_iP_{i,j}e_i\right).$$ Define $$W_j=\sum_i L_iP_{i,j}e_i.$$ Then $$B=\bigoplus_j W_j.$$ This is the key trick. Instead of maintaining every $R_i$, maintain these column summaries $W_j$. **Inserting a chord** When chord $x$ is inserted, old columns do not change. Only the new column $x$ appears. For old $i$, $$P_{i,x}=\sum_{j<x}P_{i,j}u_j,$$ where $u_j=1$ iff chord $j$ intersects chord $x$. Therefore $$\sum_i L_iP_{i,x}e_i =\sum_i L_ie_i\sum_jP_{i,j}u_j =\sum_j u_j\left(\sum_i L_iP_{i,j}e_i\right) =\sum_{j:u_j=1}W_j.$$ Also $P_{x,x}=1$, so if $L_x=1$, the new chord contributes $e_x$ to $W_x$. Thus $$W_x=\left(\bigoplus_{j<x,\ j\sim x}W_j\right)\oplus ([L_x=1]e_x).$$ Then just do $$B\leftarrow B\oplus W_x.$$ The prefix is tight-knit iff $B=0$. **Circle geometry** For chords $(a,b)$ and $(c,d)$, they intersect iff exactly one endpoint of one chord lies strictly inside the other chord. So for a new chord $(a,b)$, old intersecting chords are exactly old chords with exactly one endpoint in $(a,b)$. Use Fenwick trees with XOR: - store $L_j$ at both endpoints of chord $j$; - store $W_j$ at both endpoints of chord $j$. A query on $[a+1,b-1]$ sees a chord zero times if both endpoints are outside, twice if both are inside, and once if exactly one endpoint is inside. Since we use XOR, the first two cases vanish and the crossing chords remain. Pretty slick, honestly. **Implementation detail** The formal vectors $e_i$ are too large to store, so the code replaces each $e_i$ by a random fingerprint: two `uint64_t` values plus one parity bit. This is the intended hashing/probability part. Collision probability is tiny enough that worrying about it is mostly competitive-programming cosplay. **Algorithm** For each chord $x=(a,b)$: 1. Query `fwL` on $[a+1,b-1]$ to get $s=\sum_{j<x,\ j\sim x}L_j$. 2. Set $L_x=1\oplus s$. 3. Query `fwW` on $[a+1,b-1]$ to get $C=\bigoplus_{j<x,\ j\sim x}W_j$. 4. Set $W_x=C\oplus([L_x=1]e_x)$. 5. Toggle $B\leftarrow B\oplus W_x$. 6. Insert $L_x$ into `fwL` at both endpoints if $L_x=1$. 7. Insert $W_x$ into `fwW` at both endpoints. 8. Output `1` iff $B=0$, otherwise `0`. **Correctness** Chains are exactly directed paths in the intersection DAG, so $L_i$ and $R_i$ are respectively the path parities ending and starting at $i$. Every chain containing $i$ decomposes uniquely through $i$, hence its parity is $L_iR_i$. Thus tight-knit means no chord has both values equal to $1$. The vector $B=\sum_iL_iR_ie_i$ is exactly the set of bad chords. Since $R_i=\sum_jP_{i,j}$, rewriting the sum by columns gives $B=\bigoplus_jW_j$. When inserting $x$, old columns stay unchanged. The new column satisfies $P_{i,x}=\sum_ju_jP_{i,j}$, so its old-chord contribution is exactly $\bigoplus_{j:u_j=1}W_j$. The chord $x$ itself contributes exactly when $L_x=1$. Therefore the computed $W_x$ is correct, and XORing it into $B$ keeps $B$ correct. The Fenwick queries return exactly the intersecting old chords because intersection is equivalent to exactly one endpoint lying inside the new chord interval. Therefore every step computes the same values as the formal DP, and the answer is correct up to the negligible hash-collision probability. **Complexity** Each chord uses $O(\log n)$ Fenwick work, so the total complexity is $O(n\log n)$ per test case and $O(n)$ memory.#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Val {
unsigned long long x = 0, y = 0;
int p = 0;
};
Val operator^(const Val& a, const Val& b) {
return {a.x ^ b.x, a.y ^ b.y, a.p ^ b.p};
}
Val& operator^=(Val& a, const Val& b) {
a.x ^= b.x;
a.y ^= b.y;
a.p ^= b.p;
return a;
}
struct FenwickVal {
int n = 0;
vector<Val> bit;
FenwickVal() {}
FenwickVal(int n_) { init(n_); }
void init(int n_) {
n = n_;
bit.assign(n + 1, Val{});
}
void add(int idx, Val v) {
for (; idx <= n; idx += idx & -idx) bit[idx] ^= v;
}
Val pref(int idx) const {
Val res;
for (; idx > 0; idx -= idx & -idx) res ^= bit[idx];
return res;
}
Val range(int l, int r) const {
if (l > r) return Val{};
return pref(r) ^ pref(l - 1);
}
};
struct FenwickBit {
int n = 0;
vector<unsigned char> bit;
FenwickBit() {}
FenwickBit(int n_) { init(n_); }
void init(int n_) {
n = n_;
bit.assign(n + 1, 0);
}
void add(int idx, int v) {
if (!v) return;
for (; idx <= n; idx += idx & -idx) bit[idx] ^= 1;
}
int pref(int idx) const {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res ^= bit[idx];
return res;
}
int range(int l, int r) const {
if (l > r) return 0;
return pref(r) ^ pref(l - 1);
}
};
static unsigned long long splitmix64(unsigned long long x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
int main() {
setIO();
int T;
cin >> T;
unsigned long long seed = chrono::steady_clock::now().time_since_epoch().count();
while (T--) {
int n;
cin >> n;
FenwickBit fwL(2 * n);
FenwickVal fwW(2 * n);
Val bad;
string ans;
ans.reserve(n);
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
Val h{splitmix64(seed + 2ULL * i), splitmix64(seed + 2ULL * i + 1), 1};
int s = fwL.range(a + 1, b - 1);
int L = 1 ^ s;
Val w = fwW.range(a + 1, b - 1);
if (L) w ^= h;
bad ^= w;
if (L) {
fwL.add(a, 1);
fwL.add(b, 1);
}
fwW.add(a, w);
fwW.add(b, w);
ans.push_back((bad.x == 0 && bad.y == 0 && bad.p == 0) ? '1' : '0');
}
cout << ans << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Val {
unsigned long long x = 0, y = 0;
int p = 0;
};
Val operator^(const Val& a, const Val& b) {
return {a.x ^ b.x, a.y ^ b.y, a.p ^ b.p};
}
Val& operator^=(Val& a, const Val& b) {
a.x ^= b.x;
a.y ^= b.y;
a.p ^= b.p;
return a;
}
struct FenwickVal {
int n = 0;
vector<Val> bit;
FenwickVal() {}
FenwickVal(int n_) { init(n_); }
void init(int n_) {
n = n_;
bit.assign(n + 1, Val{});
}
void add(int idx, Val v) {
for (; idx <= n; idx += idx & -idx) bit[idx] ^= v;
}
Val pref(int idx) const {
Val res;
for (; idx > 0; idx -= idx & -idx) res ^= bit[idx];
return res;
}
Val range(int l, int r) const {
if (l > r) return Val{};
return pref(r) ^ pref(l - 1);
}
};
struct FenwickBit {
int n = 0;
vector<unsigned char> bit;
FenwickBit() {}
FenwickBit(int n_) { init(n_); }
void init(int n_) {
n = n_;
bit.assign(n + 1, 0);
}
void add(int idx, int v) {
if (!v) return;
for (; idx <= n; idx += idx & -idx) bit[idx] ^= 1;
}
int pref(int idx) const {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res ^= bit[idx];
return res;
}
int range(int l, int r) const {
if (l > r) return 0;
return pref(r) ^ pref(l - 1);
}
};
static unsigned long long splitmix64(unsigned long long x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
int main() {
setIO();
int T;
cin >> T;
unsigned long long seed = chrono::steady_clock::now().time_since_epoch().count();
while (T--) {
int n;
cin >> n;
FenwickBit fwL(2 * n);
FenwickVal fwW(2 * n);
Val bad;
string ans;
ans.reserve(n);
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
Val h{splitmix64(seed + 2ULL * i), splitmix64(seed + 2ULL * i + 1), 1};
int s = fwL.range(a + 1, b - 1);
int L = 1 ^ s;
Val w = fwW.range(a + 1, b - 1);
if (L) w ^= h;
bad ^= w;
if (L) {
fwL.add(a, 1);
fwL.add(b, 1);
}
fwW.add(a, w);
fwW.add(b, w);
ans.push_back((bad.x == 0 && bad.y == 0 && bad.p == 0) ? '1' : '0');
}
cout << ans << '\n';
}
return 0;
}