關於我想了很久才想出這題咋做這檔事 - 6
#0.0 寫在前面
自從某天之後,題解都是單篇發了,於是“關於我想了好久才想出這題咋做這檔事”這個標題就算是荒廢了,正巧最近發現暑假在 ZR 學的東西,回來補的題,由於疏於整理,根本記不住,就想著寫部落格總結一下,於是就讓這個標題借屍還魂重獲新生了。
#Prob. 1 ABC070D Transit Tree Path
Time Limit: 2s | Memory Limit: 256MB
#題意簡述
給出一棵有 \(n(n\leq10^5)\) 個結點的樹和指定節點 \(K\),給出 \(q(q\leq10^5)\) 個詢問,求結點 \(x_i\) 過結點 \(K\) 到節點 \(y_i\) 的最短距離。
#大體思路
直接以 \(K\) 為根,\(O(n)\) 處理出 \(K\) 到任意一個點的距離,詢問時直接回答距離和即可。
#Code
const int N = 100010; const int INF = 0x3fffffff; struct Edge {int u, v, nxt; ll w;} e[N << 1]; ll n, q, k, cnt(1), head[N], lg2[N], d[N]; inline void add(int u, int v, ll w) { e[cnt].u = u, e[cnt].v = v, e[cnt].w = w; e[cnt].nxt = head[u], head[u] = cnt ++; } void dfs(int x, int fa) { for (int i = head[x]; i; i = e[i].nxt) if (e[i].v != fa) { d[e[i].v] = d[x] + e[i].w; dfs(e[i].v, x); } } int main() { scanf("%lld", &n); for (int i = 1; i <= n; ++ i) lg2[i] = lg2[i >> 1] + 1; for (int i = 1; i < n; ++ i) { int u, v; ll w; scanf("%d%d%lld", &u, &v, &w); add(u, v, w); add(v, u, w); } scanf("%lld%lld", &q, &k); dfs(k, 0); while (q --) { int a, b; scanf("%d%d", &a, &b); printf("%lld\n", d[a] + d[b]); } return 0; }
#Prob. 2 CF609E Minimum spanning tree for each edge
Time Limit: 2s | Memory Limit: 256MB
#題意簡述
給你 \(n(n\leq2\cdot10^5)\) 個點,\(m(m\leq2\cdot10^5)\) 條邊,如果對於一個最小生成樹中要求必須包括第 \(i(1\leq i\leq m)\) 條邊,那麼最小生成樹的權值總和最小是多少。
#大體思路
直接考慮 Kruscal 生成樹的構造方法,每次都是將兩個連通塊用兩者之間邊權最小的邊相連,那麼我們考慮對於第 \(i\) 條邊 \(u\to v\),如果要把它包含進生成樹,那麼此時樹上一定有且僅有一個包含 \(u,v\)
我們可以 \(O(m\log m)\) 求出最小生成樹,然後 \(O(n\log n)\) 倍增處理 \(x\) 到 \(2^k\) 級祖先的路徑上的最大值,列舉每條邊 \(O(\log n)\) 回答即可。
#Code
const int N = 400010;
const int INF = 0x3fffffff;
struct Edge {
int u, v, w, id, nxt;
inline bool operator < (const Edge b) const {
return w < b.w;
}
};
Edge e[N], ue[N];
int n, m, head[N], cnt = 1, fa[N], vis[N], dep[N];
int inmst[N], sum, f[N][30], g[N][30], lg2[N], lt, ans[N];
template <typename T>
inline T Max(const T a, const T b) {
return a > b ? a : b;
}
inline void add(int u, int v, int w, int id) {
e[cnt].u = u, e[cnt].v = v, e[cnt].w = w;
e[cnt].id = id, e[cnt].nxt = head[u], head[u] = cnt ++;
e[cnt].u = v, e[cnt].v = u, e[cnt].w = w;
e[cnt].id = id, e[cnt].nxt = head[v], head[v] = cnt ++;
}
inline int find(int x) {
while (x != fa[x])
x = fa[x] = fa[fa[x]];
return x;
}
inline void kruskal() {
for (int i = 1; i <= n; ++ i) fa[i] = i;
sort(ue + 1, ue + m + 1);
for (int i = 1; i <= m; ++ i) {
int af = find(ue[i].u);
int bf = find(ue[i].v);
if (af != bf) {
add(ue[i].u, ue[i].v, ue[i].w, ue[i].id);
sum += ue[i].w, fa[af] = bf, inmst[ue[i].id] = true;
}
}
}
inline void get_log() {
lg2[1] = 0;
for (int i = 2; i <= n; ++ i)
lg2[i] = lg2[i >> 1] + 1;
}
inline void doubly() {
queue <int> q; q.push(1); vis[1] = 1, dep[1] = 1;
while (q.size()) {
int now = q.front(); q.pop();
for (int i = head[now]; i; i = e[i].nxt) {
if (vis[e[i].v]) continue;
int y = e[i].v; vis[e[i].v] = 1;
f[y][0] = now, g[y][0] = e[i].w, dep[y] = dep[now] + 1;
for (int j = 1; j <= lt; j ++) {
f[y][j] = f[f[y][j - 1]][j - 1];
g[y][j] = Max(g[y][j - 1], g[f[y][j - 1]][j - 1]);
}
q.push(y);
}
}
}
inline int query(int x, int y) {
int res = 0;
if (dep[x] < dep[y]) swap(x, y);
for (int i = lt; i >= 0; i --)
if (dep[f[x][i]] >= dep[y])
res = Max(res, g[x][i]), x = f[x][i];
if (x == y) return res;
for (int i = lt; i >= 0; -- i)
if (f[x][i] != f[y][i]) {
res = Max(res, Max(g[x][i], g[y][i]));
x = f[x][i], y = f[y][i];
}
res = Max(res, Max(g[x][0], g[y][0]));
return res;
}
signed main(){
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; ++ i) {
scanf("%lld%lld%lld", &ue[i].u, &ue[i].v, &ue[i].w);
ue[i].id = i;
}
kruskal(); get_log(); lt = lg2[n]; doubly();
for (int i = 1; i <= m; ++ i)
if (inmst[ue[i].id]) ans[ue[i].id] = sum;
else ans[ue[i].id] = sum - query(ue[i].u, ue[i].v) + ue[i].w;
for (int i = 1; i <= m; ++ i) printf("%lld\n", ans[i]);
return 0;
}
#Prob. 3 CF1301E Nanosoft
Time Limit: 2s | Memory Limit: 512MB
#題意簡述
給你一個 \(n\times m(n,m\leq500)\) 的只包含四種顏色的網格。
\(q(q\leq3\times10^5)\) 次詢問,每次問一個矩陣中所包含的形如以下格式的 Logo 的最大面積。
#大體思路
我們要求一個區間內的最大/最小值,不帶修,首先可以考慮用 st 表。
我們先定一個基準點,這裡本人選擇了紅色區域的右下角作為基準點,首先需要處理出以每個基準點為中心的位置最大範圍的合法矩形的半徑,這個可以通過對四種顏色分別做二維字首和+二分答案進行預處理。
之後就是令人《神清氣爽》的二維 st 表,當然本質上與一維並沒有太多的區別,我們指定一維的優先順序高於另一維,當優先順序高的一維大小大於 \(1\) 時就二分這一維,否則二分另一維,顯然這樣是可以得到正確的答案的。
然後我們就可以二分答案最大的半徑,然後就可以判定這個區間內的最大半徑是不是大於當前的半徑,於是就解決了這個問題。
這份程式碼寫於 2021.7.18,當我於 2021.11.1 寫本篇部落格時,突然意識到我們完全可以直接得到這個區間的最大值,為什麼要再二分一次呢?不能理解QnQ
#Code
const int N = 510;
const int INF = 0x3fffffff;
template <typename T> inline T Max(const T a, const T b) {return a > b ? a : b;}
template <typename T> inline T Min(const T a, const T b) {return a < b ? a : b;}
int n, m, q, mp[N][N], sum[5][N][N], t[N][N];
int lg2[N], st[N][N][10][10], ln, lm;
inline bool pre_check(int x, int y, int p) {
int res[5] = {0};
res[1] = sum[1][x][y] - sum[1][x - p][y] - sum[1][x][y - p] + sum[1][x - p][y - p];
res[2] = sum[2][x][y + p] - sum[2][x][y] - sum[2][x - p][y + p] + sum[2][x - p][y];
res[3] = sum[3][x + p][y + p] - sum[3][x][y + p] - sum[3][x + p][y] + sum[3][x][y];
res[4] = sum[4][x + p][y] - sum[4][x][y] - sum[4][x + p][y - p] + sum[4][x][y - p];
if (res[1] == p * p && res[2] == p * p && res[3] == p * p && res[4] == p * p)
return true;
else return false;
}
inline void get_st() {
ln = lg2[n], lm = lg2[m];
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
st[i][j][0][0] = t[i][j];
for (int i = 0; i <= ln; ++ i)
for (int j = 0; j <= lm; ++ j) {
if (!i && !j) continue;
for (int x = 1; x + (1 << i) - 1 <= n; ++ x)
for (int y = 1; y + (1 << j) - 1 <= m; ++ y) {
if (i) {
st[x][y][i][j] = Max(st[x][y][i - 1][j], st[x + (1 << (i - 1))][y][i - 1][j]);
} else {
st[x][y][i][j] = Max(st[x][y][i][j - 1], st[x][y + (1 << (j - 1))][i][j - 1]);
}
}
}
}
inline bool check(int r1, int c1, int r2, int c2, int p) {
r1 += p - 1, c1 += p - 1, r2 -= p, c2 -= p;
int k1 = lg2[r2 - r1 + 1], k2 = lg2[c2 - c1 + 1];
int res1 = st[r1][c1][k1][k2];
int res2 = st[r1][c2 - (1 << k2) + 1][k1][k2];
int res3 = st[r2 - (1 << k1) + 1][c1][k1][k2];
int res4 = st[r2 - (1 << k1) + 1][c2 - (1 << k2) + 1][k1][k2];
int res = Max(Max(res1, res2), Max(res3, res4));
if (res >= p) return true; else return false;
}
int main() {
scanf("%d%d%d", &n, &m, &q);
lg2[1] = 0;
for (int i = 2; i <= Max(n, m); ++ i)
lg2[i] = lg2[i >> 1] + 1;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
char c; cin >> c;
if (c == 'R') mp[i][j] = 1;
else if (c == 'G') mp[i][j] = 2;
else if (c == 'B') mp[i][j] = 3;
else mp[i][j] = 4;
}
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
sum[1][i][j] = sum[1][i][j - 1] + sum[1][i - 1][j] - sum[1][i - 1][j - 1] + (mp[i][j] == 1);
sum[2][i][j] = sum[2][i][j - 1] + sum[2][i - 1][j] - sum[2][i - 1][j - 1] + (mp[i][j] == 2);
sum[3][i][j] = sum[3][i][j - 1] + sum[3][i - 1][j] - sum[3][i - 1][j - 1] + (mp[i][j] == 3);
sum[4][i][j] = sum[4][i][j - 1] + sum[4][i - 1][j] - sum[4][i - 1][j - 1] + (mp[i][j] == 4);
}
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
int l = 0, res = 0;
int r = Min(Min(i, j), Min(n - i, m - j));
while (l <= r) {
int mid = (l + r) >> 1;
if (pre_check(i, j, mid))
res = mid, l = mid + 1;
else r = mid - 1;
}
t[i][j] = res;
}
get_st();
while (q --) {
int r1, c1, r2, c2, l = 0, res = 0;
scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
int r = Min((r2 - r1 + 1) >> 1, (c2 - c1 + 1) >> 1);
while (l <= r) {
int mid = (l + r) >> 1;
if (check(r1, c1, r2, c2, mid))
res = mid, l = mid + 1;
else r = mid - 1;
}
printf("%d\n", 4 * res * res);
}
return 0;
}
#Prob. 4 CF1527D MEX Tree
[題解]CF1527D MEX Tree
發表於 2021-07-26 14:24Dfkuaid
摘要:CF1527D MEX Tree 題解
閱讀全文
>>
#Prob. 5 UVA1707 Surveillance
#題意簡述
給定一個長度為 \(n(n\leq10^6\)) 的環,有 \(k(k\leq 10^6)\) 個區域被覆蓋,求最小的滿足環被完全覆蓋的區域數量。
#大體思路
遇到環上問題,我們先不考慮環,先考慮鏈。如果是鏈的話,我們直接預處理出把每個位置覆蓋住的線段最右端點是哪個位置,然後貪心就可以了。這個做法 \(O(n)\).
我們考慮斷環為鏈,複製一段接在尾端,如果我們直接列舉左端點暴力做的話是 \(O(n^2)\) ,考慮用倍增優化這個過程,預處理出走 \(2^i\) 步能走到的位置,就可以做了。時間複雜度 \(O(n\log n)\).
#Code
#define mset(l, x) memset(l, x, sizeof(l))
const int N = 3000010;
const int INF = 0x3fffffff;
template <typename T>
inline T Max(const T a, const T b) {
return a > b ? a : b;
}
template <typename T>
inline T Min(const T a, const T b) {
return a < b ? a : b;
}
int n, k, lg[N], mr[N], nxt[N], xtr[N][30], ans;
inline void clear() {
ans = INF;
mset(mr, 0); mset(nxt, 0); mset(xtr, 0);
}
int main() {
for (int i = 2; i <= 2e6; ++ i) lg[i] = lg[i >> 1] + 1;
while (scanf("%d%d", &n, &k) != EOF) {
clear();
for (int i = 1; i <= k; ++ i) {
int l, r; scanf("%d%d", &l, &r);
if (r < l) {r += n;} mr[l] = Max(mr[l], r);
}
int maxr = 0;
for (int i = 1; i <= n << 1; ++ i) {
if (mr[i]) {
if (nxt[i - 1] != INF)
nxt[i] = Max(nxt[i - 1], mr[i]);
else nxt[i] = mr[i];
maxr = Max(maxr, mr[i]);
} else if (maxr >= i) nxt[i] = nxt[i - 1];
else nxt[i] = INF;
xtr[i][0] = nxt[i] + 1;
}
for (int i = 1; i <= lg[n << 1]; ++ i) {
for (int j = 1; j <= n << 1; ++ j) {
if (xtr[j][i - 1] >= INF) xtr[j][i] = INF;
else xtr[j][i] = xtr[xtr[j][i - 1]][i - 1];
}
}
for (int i = 1; i <= n; ++ i) {
int now = i, res = 0;
for (int j = lg[n << 1]; j >= 0; -- j)
if (xtr[now][j] < i + n)
res += (1 << j), now = xtr[now][j];
if (xtr[now][0] <= INF) ans = Min(ans, res);
}
if (ans != INF) printf("%d\n", ans + 1);
else printf("impossible\n");
}
return 0;
}
#Prob. 6 POJ3419 Difference Is Beautiful
[題解]POJ3419 Difference Is Beautiful
發表於 2021-09-04 16:02Dfkuaid
摘要:POJ3419 Difference Is Beautiful 題解
閱讀全文
>>
#Prob. 7 HDU3183 A Magic Lamp
Time Limit: 2s | Memory Limit: 32MB
#題意簡述
給定一個有 \(n(n\leq1000)\) 位的數,你可以刪掉其中的任意 \(m(m\leq1000)\) 位,剩餘的數位向左合併,要求得到的新數最小。
#大體思路
轉化一下題意,我們也就是要找到 \(n-m\) 位最小的數作為答案,我們從高位到低位進行貪心,顯然第一位可以選的區間是 \([1,n-(n-m-1)]\),我們肯定要選這個區間內最小的數位,設這個數位為 \(x_1\),之後每一位依次類推,可選區間應當是 \([x_1, n-(n-m-2)],[x_2,n-(n-m-3)]\dots\),於是我們可以用 st 表提前處理,做到 \(O(1)\) 回答,時間複雜度為 \(O(n\log n)\).
#Code
const int N = 100010;
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;
}
int n, m, mn[10][N], lg[N]; char s[N], ans[N];
inline int MIN(int x, int y) {return s[x] <= s[y] ? x : y;}
inline int query(int l, int r) {
int k = lg[r - l + 1];
return MIN(mn[k][l], mn[k][r - (1 << k) + 1]);
}
int main() {
for (int i = 2; i <= 1000; ++ i) lg[i] = lg[i >> 1] + 1;
while (~scanf("%s%d", s + 1, &m)) {
n = strlen(s + 1);
for (int i = 1; i <= n; ++ i) mn[0][i] = i;
for (int i = 1; i <= lg[n]; ++ i)
for (int j = 1; j + (1 << i) - 1 <= n; ++ j)
mn[i][j] = MIN(mn[i - 1][j], mn[i - 1][j + (1 << i - 1)]);
int pos = 1, apos = 1, st = 1;
for (int i = n - m - 1; ~i; -- i) {
pos = query(pos, n - i);
ans[apos] = s[pos ++], apos ++;
}
for (st = 1; st < apos && ans[st] == '0'; ++ st) ;
if (st >= apos) {puts("0"); continue;}
for (int i = st; i < apos; ++ i) putchar(ans[i]); puts("");
}
return 0;
}