NOI2020 簡要題解
Delicacy
Solution
暴力 DP:\(f_{i,u}\) 表示 \(i\) 時刻到達 \(u\) 的最大收益,注意到轉移 \(f_{i,v}\leftarrow f_{i-w,u}\)(\(w\le 5\))對所有的 \(i\) 都一樣,可以使用矩陣快速冪,矩陣規模為 \(nw\)。
對於原問題,設 \(g_{i,u}\) 表示從小到大第 \(i\) 個時間點到達 \(u\) 的最大收益,這裡從 \(i\) 從小到大,不斷讓 \(g\) 乘上轉移矩陣 \(A\) 的 \(t_i-t_{i-1}\) 次冪之後再加上 \(x_i\) 的額外收益 \(y_i\)。
對於求一個 \(1\times n\)
總複雜度 \(O((k+nw)(nw)^2\log t)\)。
Code
#include <bits/stdc++.h> template <class T> inline void read(T &res) { res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); if (bo) res = ~res + 1; } template <class T> inline T Max(const T &a, const T &b) {return a > b ? a : b;} typedef long long ll; const int N = 55, E = 255; int n, m, T, k, c[N]; struct matrix { int n, m; ll a[E][E]; matrix() {} matrix(int _n, int _m) : n(_n), m(_m) {memset(a, -1, sizeof(a));} friend inline matrix operator * (matrix a, matrix b) { matrix res = matrix(a.n, b.m); for (int i = 1; i <= a.n; i++) for (int j = 1; j <= b.m; j++) for (int k = 1; k <= a.m; k++) if (a.a[i][k] != -1 && b.a[k][j] != -1) res.a[i][j] = Max(res.a[i][j], a.a[i][k] + b.a[k][j]); return res; } } F, A[32]; struct orz { int t, x, y; } a[E]; inline bool comp(orz a, orz b) { return a.t < b.t; } int o(int u, int id) {return (u - 1) * 5 + id;} int main() { #ifdef ONLINE_JUDGE freopen("delicacy.in", "r", stdin); freopen("delicacy.out", "w", stdout); #endif int x, y, z; read(n); read(m); read(T); read(k); for (int i = 1; i <= n; i++) read(c[i]); A[0] = matrix(n * 5, n * 5); F = matrix(1, n * 5); while (m--) read(x), read(y), read(z), A[0].a[o(x, z)][o(y, 1)] = Max(A[0].a[o(x, z)][o(y, 1)], 1ll * c[y]); for (int u = 1; u <= n; u++) for (int id = 1; id <= 4; id++) A[0].a[o(u, id)][o(u, id + 1)] = 0; F.a[1][o(1, 1)] = c[1]; for (int i = 1; i <= k; i++) read(a[i].t), read(a[i].x), read(a[i].y); std::sort(a + 1, a + k + 1, comp); a[0] = (orz) {0, 1, 0}; a[++k] = (orz) {T, 1, 0}; for (int i = 1; i <= 29; i++) A[i] = A[i - 1] * A[i - 1]; for (int i = 1; i <= k; i++) { for (int j = 29; j >= 0; j--) if ((a[i].t - a[i - 1].t >> j) & 1) F = F * A[j]; if (F.a[1][o(a[i].x, 1)] > 0) F.a[1][o(a[i].x, 1)] += a[i].y; } return std::cout << F.a[1][o(1, 1)] << std::endl, 0; }
Destiny
Solution
為了方便,選邊改成選點(根不能選),即 \(u_i\) 到 \(v_i\) 的路徑上(含 \(v_i\),不含 \(u_i\))至少要選一個點。
考慮一個 DP:\(f_{u,i}\) 表示 \(u\) 的祖先中選出的最深的點深度為 \(i\),覆蓋終點在 \(u\) 子樹內的所有路徑的方案數,需要滿足 \(1\le i\le\max(1,dep_u-1)\)。
轉移一:不選 \(u\) 點。(\(md_u\) 表示滿足 \(v_i=u\) 的最大 \(dep_{u_i}\))
\[f_{u,i}=\prod_{v\in son(u)}f_{v,i}\>\>\>(md_u<i\le dep_u) \]轉移二:選上 \(u\) 點。
\[f_{u,i}+=f_{u,dep_u}\>\>\>(i<dep_u) \]之後如果 \(u\ne 1\) 就 \(f_{u,dep_u}\leftarrow0\),就能得到 \(f_u\)。
這樣做完答案就是 \(f_{1,1}\),複雜度 \(O(n^2)\)。
注意到第一種轉移式左右的下標不變,第二種轉移是區間加,故可以使用線段樹合併,維護加和乘標記,區間清 \(0\) 時只需刪掉對應節點即可,\(O(n\log n)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
const int N = 5e5 + 5, M = N << 1, L = 6e7 + 5, EI = 998244353;
int n, m, ecnt, nxt[M], adj[N], go[M], dep[N], md[N], ToT;
struct node
{
int lc, rc, add, prod;
} T[L];
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void dfs(int u, int fu)
{
dep[u] = dep[fu] + 1;
for (int e = adj[u], v; e; e = nxt[e])
if ((v = go[e]) != fu) dfs(v, u);
}
void down(int p)
{
if (!T[p].lc) T[T[p].lc = ++ToT].prod = 1;
if (!T[p].rc) T[T[p].rc = ++ToT].prod = 1;
T[T[p].lc].prod = 1ll * T[T[p].lc].prod * T[p].prod % EI;
T[T[p].rc].prod = 1ll * T[T[p].rc].prod * T[p].prod % EI;
T[T[p].lc].add = (1ll * T[T[p].lc].add * T[p].prod + T[p].add) % EI;
T[T[p].rc].add = (1ll * T[T[p].rc].add * T[p].prod + T[p].add) % EI;
T[p].add = 0; T[p].prod = 1;
}
int query(int l, int r, int pos, int p)
{
if (!p) return 0;
if (l == r) return T[p].add;
int mid = l + r >> 1, res = pos <= mid ? query(l, mid, pos, T[p].lc)
: query(mid + 1, r, pos, T[p].rc);
res = (1ll * res * T[p].prod + T[p].add) % EI;
return res;
}
void del(int l, int r, int s, int e, int &p)
{
if (e < l || s > r) return;
if (s <= l && r <= e) return (void) (p = 0);
int mid = l + r >> 1; down(p);
del(l, mid, s, e, T[p].lc); del(mid + 1, r, s, e, T[p].rc);
}
int merge(int x, int y)
{
if (!x || !y) return 0;
if (!T[x].lc && !T[x].rc)
return T[y].prod = 1ll * T[y].prod * T[x].add % EI,
T[y].add = 1ll * T[y].add * T[x].add % EI, y;
if (!T[y].lc && !T[y].rc)
return T[x].prod = 1ll * T[x].prod * T[y].add % EI,
T[x].add = 1ll * T[x].add * T[y].add % EI, x;
down(x); down(y);
T[x].lc = merge(T[x].lc, T[y].lc);
T[x].rc = merge(T[x].rc, T[y].rc);
return x;
}
void change(int l, int r, int s, int e, int v, int &p)
{
if (e < l || s > r) return;
if (!p) T[p = ++ToT].prod = 1;
if (s <= l && r <= e) return (void) (T[p].add = (T[p].add + v) % EI);
int mid = l + r >> 1; down(p);
change(l, mid, s, e, v, T[p].lc);
change(mid + 1, r, s, e, v, T[p].rc);
}
int dp(int u, int fu)
{
int rt = 0; change(1, n, 1, n, 1, rt);
for (int e = adj[u], v; e; e = nxt[e], v = go[e])
if ((v = go[e]) != fu) rt = merge(rt, dp(v, u));
if (md[u]) del(1, n, 1, md[u], rt);
if (u > 1) change(1, n, 1, dep[u] - 1, query(1, n, dep[u], rt), rt),
del(1, n, dep[u], dep[u], rt);
return rt;
}
int main()
{
#ifdef ONLINE_JUDGE
freopen("destiny.in", "r", stdin);
freopen("destiny.out", "w", stdout);
#endif
int x, y;
read(n);
for (int i = 1; i < n; i++) read(x), read(y), add_edge(x, y);
dfs(1, 1); read(m);
while (m--) read(x), read(y), md[y] = Max(md[y], dep[x]);
return std::cout << query(1, n, 1, dp(1, 0)) << std::endl, 0;
}
Tears
Solution
這裡提供一個分塊的做法。
為了方便,下面 \(r_{i,1},r_{i,2},c_{i,1},c_{i,2}\) 分別用 \(lx,rx,ly,ry\) 代替。
考慮把二維矩形拆成 \(4\) 個字首容斥的形式,設 \(f(x,y)\) 表示矩形 \((1,1)-(x,y)\) 內滿足關係的點對數,\(g(lx_1,rx_1,ly_1,ry_1,lx_2,rx_2,ly_2,ry_2)\) 表示第一個點在矩形 \((lx_1,ly_1)-(rx_1,ry_1)\) 內,第二個點在矩形 \((lx_2,ly_2)-(rx_2,ry_2)\) 內,合法的點對數。
則大力容斥可以得到答案可以表示成:
\[f(rx,ry)-f(lx-1,ry)-f(rx,ly-1)+f(lx-1,ly-1) \]\[-g(1,lx-1,1,ry,lx,rx,1,ry)+g(1,lx-1,1,ly-1,lx,rx,1,ly-1) \]\[-g(1,rx,1,ly-1,1,rx,ly,ry)+g(1,lx-1,1,ly-1,1,lx-1,ly,ry) \]\[+g(1,lx-1,1,ly-1,lx,rx,ly,ry) \]\(f\) 可以直接二維數點,\(g(1,lx-1,1,ly-1,lx,rx,ly,ry)\) 的第一個矩形嚴格在第二個矩形左下方,可以直接用兩個矩形內的點數乘起來。
於是問題轉化成給定 \(y,l,r\),求縱座標 \(\le y\) 的區域內,第一個點的橫座標在 \([1,l-1]\),第二個點的橫座標在 \([l,r]\) 的點對數。
考慮按 \(y\) 分塊。設 \(y\) 在第 \(b\) 塊內,分幾類:、
(1)兩個點來自不同的塊,且都不在第 \(b\) 塊:列舉第二個點所在的塊,容易發現這種情況對答案的貢獻可以描述成兩個矩形內點數的乘積,直接計算即可。
(2)第二個點在第 \(b\) 塊,第一個點不在第 \(b\) 塊:與(1)類似,只不過第二個點的縱座標有了個 \(y\) 的限制,這裡可以暴力列舉第 \(b\) 塊內的所有點進行統計。
(3)兩個點都在第 \(i\ne b\) 塊:由於一個塊內的點數為 \(O(\sqrt n)\),故對於一個特定的 \(i\),本質不同的詢問只有 \(O(n)\) 種,可以事先預處理。
(4)兩個點都在第 \(b\) 塊:暴力列舉第 \(b\) 塊內的點即可。為了避免算順序對的複雜度多 \(\log\),需要把塊內的點提前按 \(y\) 排序,每次詢問時用雙指標掃。
具體實現時可以離線後從小到大列舉 \(b\),每到一個 \(b\) 就對所有相關詢問都處理一遍,這樣可以使得空間為 \(O(n+m)\),具體細節見程式碼 jiejuediao
函式。
時間複雜度 \(O((n+m)\sqrt n)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 1e5 + 5, M = N << 1, L = M << 1, E = 350;
int n, m, S, p[N], q[N], c1[M], c2[M], B[N], tot, bl[E], br[E], in[N], cur[N],
lx[M], rx[M], ly[M], ry[M], sum[N], cnt, a[E], b[E][E], lt[N];
ll ans[M], A[N], f[N];
void changeA(int x, ll v)
{
for (; x <= n; x += x & -x)
A[x] += v;
}
ll askA(int x)
{
ll res = 0;
for (; x; x -= x & -x) res += A[x];
return res;
}
void changeB(int x, int v)
{
for (; x <= n; x += x & -x)
B[x] += v;
}
int askB(int x)
{
int res = 0;
for (; x; x -= x & -x) res += B[x];
return res;
}
struct query
{
int id, t, l, r;
} que1[L];
void jiejuediao()
{
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= tot; i++)
{
cnt = 0;
for (int j = 1; j <= n; j++)
{
cur[j] = cur[j - 1]; lt[j] = lt[j - 1];
if (in[p[j]] == i) cur[j]++, a[++cnt] = j, lt[j] = cnt;
}
for (int j = 1; j <= cnt; j++)
for (int k = 1; k <= cnt; k++)
b[j][k] = b[j - 1][k] + b[j][k - 1] - b[j - 1][k - 1]
+ (p[a[j]] < p[a[k]]);
std::sort(a + 1, a + cnt + 1, [&](int x, int y) {return p[x] < p[y];});
for (int j = 1; j <= m; j++)
{
int l = lx[j] - 1, r = rx[j];
if (in[ry[j]] > i) ans[j] -= 1ll * (cur[r] - cur[l])
* sum[l] + b[lt[l]][lt[r]] - b[lt[l]][lt[l]];
if (in[ly[j] - 1] > i) ans[j] += 1ll * (cur[r] - cur[l])
* sum[l] + b[lt[l]][lt[r]] - b[lt[l]][lt[l]];
if (in[ry[j]] == i)
{
int s = 0;
for (int k = 1, h = 1, c = 0; k <= cnt && p[a[k]] <= ry[j]; k++)
if (l < a[k] && a[k] <= r)
{
s++;
while (h <= cnt && p[a[h]] < p[a[k]])
{
if (a[h] <= l) c++;
h++;
}
ans[j] -= c;
}
ans[j] -= 1ll * s * sum[l];
}
if (in[ly[j] - 1] == i)
{
int s = 0;
for (int k = 1, h = 1, c = 0; k <= cnt && p[a[k]] < ly[j]; k++)
if (l < a[k] && a[k] <= r)
{
s++;
while (h <= cnt && p[a[h]] < p[a[k]])
{
if (a[h] <= l) c++;
h++;
}
ans[j] += c;
}
ans[j] += 1ll * s * sum[l];
}
}
for (int j = 1; j <= n; j++) sum[j] += cur[j];
}
}
int main()
{
#ifdef ONLINE_JUDGE
freopen("tears.in", "r", stdin);
freopen("tears.out", "w", stdout);
#endif
int xl, xr, yl, yr;
read(n); read(m); S = sqrt(n);
for (int i = 1; i <= n; i += S) bl[++tot] = i,
br[tot] = i + S - 1 < n ? i + S - 1 : n;
for (int i = 1; i <= n; i++) read(p[i]), q[p[i]] = i,
in[i] = (i - 1) / S + 1;
for (int i = 1; i <= n; i++) f[i] = askB(p[i]), changeB(p[i], 1);
for (int i = 1; i <= m; i++)
{
read(xl); read(xr); read(yl); read(yr);
lx[i] = xl; rx[i] = xr; ly[i] = yl; ry[i] = yr;
que1[i] = (query) {-i, xl - 1, yl, yr};
que1[i + m] = (query) {i, xr, yl, yr};
}
std::sort(que1 + 1, que1 + (m << 1) + 1, [&](query a, query b)
{return a.t < b.t;});
memset(B, 0, sizeof(B));
for (int i = 1, j = 1; i <= (m << 1); i++)
{
while (j <= que1[i].t) changeA(p[j], f[j]), changeB(p[j], 1), j++;
ll delta = askA(que1[i].r) - askA(que1[i].l - 1);
int d1 = askB(que1[i].r), d2 = askB(que1[i].l - 1);
if (que1[i].id < 0) ans[-que1[i].id] -= delta, c1[-que1[i].id] = d2,
c2[-que1[i].id] -= d1 - d2;
else ans[que1[i].id] += delta, c2[que1[i].id] += d1 - d2;
}
jiejuediao();
for (int i = 1; i <= n; i++) p[i] = q[i];
for (int i = 1; i <= m; i++) std::swap(lx[i], ly[i]),
std::swap(rx[i], ry[i]);
jiejuediao();
for (int i = 1; i <= m; i++)
printf("%lld\n", ans[i] + 1ll * c1[i] * c2[i]);
return 0;
}
Meal
Solution
結論 \(1\):若 \(m=n-1\) 則合法方案一定存在。
證明:容易發現最小的數不超過 \(k\),最小數和最大數之和不少於 \(k\)(\(n\ge 2\)),當 \(n>2\) 時取走最小數加上最大數的一部分湊成 \(k\),就能得到 \(n\) 和 \(m\) 都減少 \(1\) 的情況,然後繼續這樣的操作直到 \(m\) 變成 \(0\)。
於是 \(m\ge n-1\) 時可以加入 \(m-n-1\) 個 \(0\),用 std::set
維護當前的數,就能做到 \(O(m\log m)\)。
結論 \(2\):若 \(m=n-2\) 則可以把這 \(n\) 個數拆成兩個滿足 \(m=n-1\) 的集合。
證明:考慮在每次操作的兩個數之間連邊構成一個圖,顯然 \(n\) 點 \(n-2\) 邊的圖不連通,且存在一個連通塊是樹,讓這棵樹成為其中一個集合,就能達到目的。
一個子集 \(S\) 合法的條件為 \(\sum_{i\in S}d_i=(|S|-1)k\),移一下項得 \(\sum_{i\in S}(k-d_i)=k\)。
可以通過 bitset
優化揹包 DP 來得到是否存在這樣的子集,把 DP 倒著推回去可以得到一個合法子集,這裡的複雜度為 \(O(\frac{n^3}{\omega})\)。
然後對兩個子集分別跑一遍 \(m=n-1\) 即可。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 505, M = 5e6 + 5;
int n, m, k, d[N];
std::bitset<M> f[N];
struct orz
{
int i, x, j, y;
friend inline bool operator < (orz a, orz b)
{
return a.x < b.x;
}
};
std::vector<orz> ans;
void nealchen(std::vector<orz> d, int m)
{
while (d.size() <= m) d.push_back((orz) {0, 0, 0, 0});
std::multiset<orz> pq;
for (int i = 0; i < d.size(); i++) pq.insert(d[i]);
while (m--)
{
std::multiset<orz>::iterator fi = pq.begin(), ls = pq.end(); ls--;
int t1 = ls->i, t2 = ls->x - (k - fi->x);
ans.push_back((orz) {fi->i, fi->x, ls->i, k - fi->x});
pq.erase(fi); pq.erase(ls); pq.insert((orz) {t1, t2, 0, 0});
}
}
void output()
{
for (int i = 0; i < ans.size(); i++)
{
if (ans[i].x) printf("%d %d ", ans[i].i, ans[i].x);
if (ans[i].y) printf("%d %d ", ans[i].j, ans[i].y);
puts("");
}
}
void work()
{
read(n); read(m); read(k); ans.clear();
for (int i = 1; i <= n; i++) read(d[i]);
if (m >= n - 1)
{
std::vector<orz> a;
for (int i = 1; i <= n; i++) a.push_back((orz) {i, d[i], 0, 0});
nealchen(a, m); return output();
}
std::vector<orz> ia, ib; f[0].reset(); f[0][n * k] = 1;
for (int i = 1; i <= n; i++)
{
f[i] = f[i - 1];
if (k - d[i] >= 0) f[i] |= f[i - 1] << k - d[i];
else f[i] |= f[i - 1] >> d[i] - k;
}
if (!f[n][(n + 1) * k]) return (void) puts("-1");
for (int i = n, cur = (n + 1) * k; i >= 1; i--)
if (f[i - 1][cur]) ia.push_back((orz) {i, d[i], 0, 0});
else ib.push_back((orz) {i, d[i], 0, 0}), cur -= k - d[i];
if (ia.size() > 1) nealchen(ia, ia.size() - 1);
if (ib.size() > 1) nealchen(ib, ib.size() - 1);
output();
}
int main()
{
#ifdef ONLINE_JUDGE
freopen("dish.in", "r", stdin);
freopen("dish.out", "w", stdout);
#endif
int T; read(T);
while (T--) work();
return 0;
}
Surreal
Solution
出題人:可能有的選手第一遍會看錯成葉子能隨便加。對於葉子能隨便加的版本,大概會稍微好做一點點。
題目要求的就是是否對於所有的深度 \(h\) 都存在一棵樹不能被所有模板樹生長得到。
結論:深度為 \(h\) 的不能被生長得到的樹若存在,則存在一棵樹,滿足這棵樹能夠在一條深度為 \(h\) 的鏈上的部分位置各插入一個葉子得到。
證明略。
顯然地,有了這個結論之後,我們就只要保留具有這種結構的模板樹。
一棵具有這種結構的樹可以用一個數字串表示:從上到下,\(0\) 表示沒有右子樹,\(1\) 表示右子樹為葉子,\(2\) 表示沒有左子樹,\(3\) 表示左子樹為葉子,\(4\) 表示左右子樹都為葉子。
這樣每棵模板樹都可以被表示成一個 \(0\sim3\) 的數字串(最後一個數可以為 \(4\))。
這個 \(4\) 很討厭,但不難發現在這種情況下模板樹如果要生長,則這個 \(4\) 必然要變成 \(1\) 或 \(3\),所以形如 \(s4\) 的模板樹可以拆成兩棵,即 \(s1\) 和 \(s3\)。
這樣問題就轉化成對於一個足夠大的 \(l\),是否滿足所有串長為 \(l\) 的 \(0\sim3\) 數字串都包含模板串之一為字首。
Trie 上 DFS 即可,複雜度是線性的。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 2e6 + 5, M = 1e7 + 5;
int n, cnt, lc[N], rc[N], sze[N], ToT;
bool is, f[M];
std::vector<int> seq[N];
struct trie
{
int son[4]; bool mark;
void init() {son[0] = son[1] = son[2] = son[3] = 0; mark = 0;}
} T[M];
void dfs(int u)
{
sze[u] = 1;
if (lc[u]) dfs(lc[u]), sze[u] += sze[lc[u]];
if (rc[u]) dfs(rc[u]), sze[u] += sze[rc[u]];
if (sze[lc[u]] > 1 && sze[rc[u]] > 1) is = 0;
}
void sfd(int u, int th)
{
if (lc[u] && !rc[u]) seq[th].push_back(0), sfd(lc[u], th);
if (sze[lc[u]] > 1 && sze[rc[u]] == 1) seq[th].push_back(1), sfd(lc[u], th);
if (rc[u] && !lc[u]) seq[th].push_back(2), sfd(rc[u], th);
if (sze[rc[u]] > 1 && sze[lc[u]] == 1) seq[th].push_back(3), sfd(rc[u], th);
if (sze[lc[u]] == 1 && sze[rc[u]] == 1)
seq[th].push_back(5);
}
void ins(std::vector<int> s)
{
int u = 1;
for (int i = 0; i < s.size(); i++)
{
if (!T[u].son[s[i]]) T[T[u].son[s[i]] = ++ToT].init();
u = T[u].son[s[i]];
}
T[u].mark = 1;
}
void solve(int u)
{
f[u] = T[u].mark; bool o = 1;
for (int c = 0; c < 4; c++)
{
if (T[u].son[c]) solve(T[u].son[c]);
o &= f[T[u].son[c]];
}
f[u] |= o;
}
void work()
{
int m; read(m); cnt = 0;
while (m--)
{
read(n);
for (int i = 1; i <= n; i++) read(lc[i]), read(rc[i]);
is = 1; dfs(1);
if (is) seq[++cnt].clear(), sfd(1, cnt);
}
T[ToT = 1].init();
for (int i = 1; i <= cnt; i++)
if (!seq[i].empty() && seq[i][seq[i].size() - 1] == 5)
{
seq[i][seq[i].size() - 1] = 1; ins(seq[i]);
seq[i][seq[i].size() - 1] = 3; ins(seq[i]);
}
else ins(seq[i]);
puts((solve(1), f[1]) ? "Almost Complete" : "No");
}
int main()
{
#ifdef ONLINE_JUDGE
freopen("surreal.in", "r", stdin);
freopen("surreal.out", "w", stdout);
#endif
int T; read(T);
while (T--) work();
return 0;
}
Road
不會,咕了。