[考試總結]ZROI-21-NOIP-CSP7連-DAY6 總結
#T1 聚會
#題意簡述
給定一個長度為 \(n(n\leq5\times10^5)\) 的 01 串,一個位置 \(x\) 的價值為 \(x\) 到離它最近 1 的距離,問價值和。多組資料。
#大體思路
從兩個方向分別掃一遍即可。
#Code
#define ll long long const int N = 5e5; const ll INF = 1e12; char s[N]; int t, n, a[N]; ll f[N], ans; int main() { scanf("%d", &t); for (int i = 1; i <= t; ++ i) { printf("Case #%d: ", i); memset(f, 0x3f, sizeof(f)); scanf("%d%s", &n, s); ans = 0; for (int i = 0; i < n; ++ i) a[i + 1] = s[i] - '0'; ll lst = -INF; for (int i = 1; i <= n; ++ i) { if (a[i]) lst = i; f[i] = min(f[i], (ll)i - lst); } lst = INF; for (int i = n; i >= 1; -- i) { if (a[i]) lst = i; f[i] = min(f[i], lst - (ll)i); } for (int i = 1; i <= n; ++ i) ans += f[i]; printf("%lld\n", ans); } return 0; }
#T2 跳房子
#題意簡述
一個長為 \(n(n\leq1000)\) 的嚴格遞增的序列 \(\{x_i\}(x_i\leq10^{18})\),當且僅當 \(i<j\) 且 \(x_i|x_j\) 時由 \(i\) 向 \(j\) 連邊,得到一個有向無環圖,每條邊邊可以染成三種不同的顏色,要求不允許出現連續的長度大於 \(3\) 的相同顏色的邊出現,輸出染色方案。
#大體思路
定義函式 \(f(x)\),有 \(2^{f(x)}\leq x<2^{f(x)+1}\),按如下方式進行染色:
- 如果有 \(\left\lfloor\dfrac {f(x_i)} 4\right\rfloor=\left\lfloor\dfrac {f(x_j)} 4\right\rfloor\)
- 如果有 \(\left\lfloor\dfrac {f(x_i)} {16}\right\rfloor=\left\lfloor\dfrac {f(x_j)} {16}\right\rfloor\),那麼將 \(i\to j\) 標為 \(2\);
- 其餘的標為 \(3\)。
正確性從二進位制考慮,顯然這樣分,就是每四位分為一小組,每四小組分為一大組,總共 \(4\) 大組,每一小組內的最長路徑經過不超過 \(4\) 個點,同樣的,最長的、將四個大組全部相連的路徑長度不超過 \(3\)。
#Code
#define ll long long const int N = 1000010; const int M = 1010; template <typename T> inline void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } ll n, f[N], x[N]; inline int calc(int a, int b) { if (f[a] / 4 == f[b] / 4) return 1; else if (f[a] / 16 == f[b] / 16) return 2; else return 3; } int main() { read(n); for (int i = 1; i <= n; ++ i) read(x[i]); for (int i = 1; i <= n; ++ i) { f[i] = f[i - 1]; while ((1ll << f[i]) <= x[i]) ++ f[i]; -- f[i]; } for (int i = 2; i <= n; ++ i) { for (int j = 1; j < i; ++ j) printf("%d ", calc(j, i)); puts(""); } return 0; }
#T3 人結
#題意簡述
圓上有 \(n(3\leq n\leq500)\) 個點,順時針編號為 \(1,2,\dots,n\),每個點與兩個點相連,要求通過移動點在圓上的位置,使得最終的邊沒有交叉的一個環。
#大體思路
注意到當且僅當原圖不聯通時無解;而當有解時最終得到的環的形態是唯一的,又注意到操作的可逆性,於是轉化為將一個 \(n\)-排列環變成順時針方向為 \(1,2,\dots,n\) 的環。
考慮複製一遍斷環為鏈,然後為保證次數最少,一定是一次就把一個不在自己位置上的數放到自己位置上,於是就變為在 \(2n\) 長度的序列上選中長度為 \(n\) 的段,求這一段的最長上升子序列長度,取最大值即可。
由於不能保證最開始得到的環是順時針方向,所以需要反轉後在求一遍。時間複雜度 \(O(n^2\log n)\).
#Code
#define mset(l, x) memset(l, x, sizeof(l))
const int N = 1010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> inline T Max(T &a, T &b) {return a > b ? a : b;}
template <typename T> inline T Min(T &a, T &b) {return a < b ? a : b;}
struct Edge {int u, v, nxt;} e[N];
int n, a[N], head[N], ecnt(1), vis[N], tot, ans;
inline void add_edge(int u, int v) {
e[ecnt].u = u, e[ecnt].v = v;
e[ecnt].nxt = head[u], head[u] = ecnt ++;
}
void get_ring(int x) {
vis[x] = true; a[++ tot] = x;
for (int i = head[x]; i; i = e[i].nxt)
if (!vis[e[i].v]) get_ring(e[i].v);
}
#define lb(t, l, x) lower_bound(t + 1, t + l + 1, x)
void get_ans() {
int f[N], st[N], stp;
for (int i = 1; i <= n; ++ i) a[i + n] = a[i];
for (int i = 1; i <= n; ++ i) {
mset(f, 0); mset(st, 0); stp = 0;
for (int j = i; j <= i + n - 1; ++ j) {
if (a[j] > st[stp]) st[++ stp] = a[j];
else {
int pos = lb(st, stp, a[j]) - st;
st[pos] = a[j];
}
f[j - i + 1] = stp;
}
ans = Max(ans, f[n]);
}
}
int main() {
while (1) {
read(n); if (!n) break;
mset(head, 0); ecnt = 1, tot = ans = 0;
for (int i = 1; i <= n; ++ i) {
int x, y; read(x), read(y);
add_edge(i, x); add_edge(i, y);
}
mset(vis, 0); mset(a, 0); get_ring(1);
if (tot != n) {printf("Not solvable.\n"); continue;}
else printf("Knot solvable.\n");
get_ans(); reverse(a + 2, a + n + 1);
get_ans(); printf("%d\n", n - ans);
}
return 0;
}
#T4 辣椒
#題意簡述
將一棵 \(n(n\leq2\times10^5)\) 個點的樹通過斷掉兩條邊變為三部分,定義差值為三部分中的最大大小減去最小大小,求最小差值。
#大體思路
先來考慮 \(O(n^2)\) 做法,即列舉兩個點 \(x,y\),表示將這兩個點向父親的邊斷掉,則三部分的大小有以下兩種情況(記 \(siz_x\) 為以 \(x\) 為根的子樹大小):
- \(y\) 是 \(x\) 的祖先,那麼大小分別為 \(siz_x,siz_y-siz_x,n-siz_y\);
- \(y\) 與 \(x\) 無祖孫關係,那麼大小分別為 \(siz_x,siz_y,n-siz_x-siz_y\);
直接進行維護即可。
來考慮優化,假如當前選擇的點為 \(x\),那麼考慮從根到 \(x\) 路徑上的點,需要在其中找到一個點 \(x\) 使得 \(|siz_y-\dfrac{n+siz_x}2|\) 最小,這個點集可以用 set
維護,然後直接 lower_bound()
查詢即可,當從某個點向下深入時需要將該點加入該集合;
同樣的,維護一個已經處理過但不是 \(x\) 的祖先的點集,需要在其中找到一個點 \(y\) 使得 \(|siz_y-\dfrac{n-siz_x}2|\) 儘可能小,同樣可用 set
維護,注意當從一個點退出時,需要將該點從直系集合中取出,加入旁系集合。
時間複雜度為 \(O(n\log n)\).
#Code
const int N = 500010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> inline T abs(T &x) {return x < 0 ? -x : x;}
template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}
template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}
struct Edge {int u, v, nxt;} e[N];
int n, head[N], ecnt(1), siz[N], ans = INF;
inline void add_edge(int u, int v) {
e[ecnt].u = u, e[ecnt].v = v;
e[ecnt].nxt = head[u], head[u] = ecnt ++;
}
inline int calc(int x, int y, int z) {
return Max(x, Max(y, z)) - Min(x, Min(y, z));
}
void get_size(int x, int f) {
siz[x] = 1;
for (int i = head[x]; i; i = e[i].nxt) {
if (e[i].v == f) continue;
get_size(e[i].v, x);
siz[x] += siz[e[i].v];
}
}
set <int> lineal, colla, neg_lineal, neg_colla;
void solve(int x, int f) {
if (!lineal.empty()) {
int pos = *lineal.lower_bound((n + siz[x]) / 2);
ans = Min(ans, calc(n - pos, siz[x], pos - siz[x]));
}
if (!colla.empty()) {
int pos = *colla.lower_bound((n - siz[x]) / 2);
ans = Min(ans, calc(siz[x], pos, n - siz[x] - pos));
}
if (!neg_lineal.empty()) {
int pos = -(*neg_lineal.lower_bound(-(n + siz[x]) / 2));
ans = Min(ans, calc(n - pos, siz[x], pos - siz[x]));
}
if (!neg_colla.empty()) {
int pos = -(*neg_colla.lower_bound(-(n - siz[x]) / 2));
ans = Min(ans, calc(siz[x], pos, n - siz[x] - pos));
}
lineal.insert(siz[x]); neg_lineal.insert(-siz[x]);
for (int i = head[x]; i; i = e[i].nxt)
if (e[i].v != f) solve(e[i].v, x);
lineal.erase(siz[x]); neg_lineal.erase(-siz[x]);
colla.insert(siz[x]); neg_colla.insert(-siz[x]);
}
int main() {
read(n);
for (int i = 1; i < n; ++ i) {
int u, v; read(u), read(v);
add_edge(u, v); add_edge(v, u);
}
get_size(1, 0); solve(1, 0);
printf("%d", ans); return 0;
}