CLYZ-NOIP十連測 Day1
CLYZ-NOIP2021 國慶集訓 B 組 Day1
題面:https://files.cnblogs.com/files/blogs/575626/day1B.zip
撰寫部落格
感覺很厲害,不明覺厲
我們令 \(dp_i\) 狀態表示強制選擇刪除 \(i\) 位置並且幹掉了 \(i\) 之前的和 \(i\) 所覆蓋的線段的最小花費。
考慮狀態轉移,很顯然就是
\[dp_i=\min_{pre_{i-1}}^{i-1}{dp_j}+w_i \]其中 \(pre_{i}\) 為右端點為 \(i\) 的線段中,左端點最大值的字首max。可以用 \(\text{KMP}\) 演算法來預處理出來。
然後用單調佇列轉移即可。
時間複雜度為 \(O(n)\)
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f, N = 2e5 + 10; inline long long read() { long long ret = 0; char ch = ' ', c = getchar(); while (!(c >= '0' && c <= '9')) ch = c, c = getchar(); while (c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0', c = getchar(); return ch == '-' ? -ret : ret; } int n, m, nxt[N], pre[N]; char s[N], c[11][N]; int a[N], dp[N], q[N], l = 1, r; void pretreat(int op) { int j = 0, len = strlen(c[op] + 1); for (int i = 1; i < len; i++) { while (j && c[op][j + 1] != c[op][i + 1]) j = nxt[j]; if (c[op][j + 1] == c[op][i + 1]) j++; nxt[i + 1] = j; } } void kmp(int op) { int j = 0, len = strlen(s + 1), lenc = strlen(c[op] + 1); for (int i = 0; i < len; i++) { pre[i + 1] = max(pre[i], pre[i + 1]); while (j && c[op][j + 1] != s[i + 1]) j = nxt[j]; if (c[op][j + 1] == s[i + 1]) j++; if (j == lenc) pre[i + 1] = max(pre[i + 1], i + 1 - lenc + 1), j = nxt[j]; } } int main() { freopen("wzadx.in", "r", stdin); freopen("wzadx.out", "w", stdout); n = read(), m = read(); scanf("%s", s + 1); for (int i = 1; i <= n; i++) a[i] = read(); for (int i = 1; i <= m; i++) { scanf("%s", c[i] + 1); pretreat(i); kmp(i); } n++; l = 1, r = 0; for (int i = 0; i <= n; i++) { while (l <= r && q[l] < pre[i - 1]) l++; dp[i] = dp[q[l]] + a[i]; while (l <= r && dp[q[r]] >= dp[i]) r--; q[++r] = i; } printf("%d", dp[n]); return 0; }
逛動物園
我們考慮這玩意,每個籠子最開始的答案應該是 \(3^n\)。
然後發現每次合併,左邊的應該會乘上 \(\frac{2}{3}\),右邊的會乘上 \(\frac{1}{3}\)。
考慮離線下來,然後整一個類似於克魯斯卡爾重構樹的結構,用線段樹維護 \(dfs\) 序列即可了。
似乎並沒有什麼方法可以在低於 \(O(n\log n)\) 的複雜度內解決。
#include <bits/stdc++.h> using std::cerr; using std::cin; using std::cout; using std::vector; const int N = 4e5 + 10, mod = 998244353; int n, m, f[N], op[N], x[N], y[N], dfc, st[N], ed[N], sz[N], F[N]; vector<int> v[N]; int ksm(int x, int y) { int res = 1; for (; y; y /= 2, x = (long long)x * x % mod) { if (y & 1) { res = (long long)res * x % mod; } } return res; } int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void dfs(int u) { st[u] = dfc + 1; if (u <= n && u) { ed[u] = ++dfc; return; } for (auto &j : v[u]) { dfs(j); } ed[u] = dfc; } inline int ls(int k) { return k << 1; } inline int rs(int k) { return k << 1 | 1; } int val[N * 4]; inline void down(int k) { if (val[k] != 1) { val[ls(k)] = (long long)val[ls(k)] * val[k] % mod; val[rs(k)] = (long long)val[rs(k)] * val[k] % mod; val[k] = 1; } } void modify(int k, int l, int r, int ql, int qr, int x) { if (ql <= l && r <= qr) { val[k] = (long long)val[k] * x % mod; return; } down(k); int mid = (l + r) >> 1; if (ql <= mid) { modify(ls(k), l, mid, ql, qr, x); } if (mid < qr) { modify(rs(k), mid + 1, r, ql, qr, x); } return; } int query(int k, int l, int r, int x) { if (l == r) { return val[k]; } down(k); int mid = (l + r) >> 1; return x <= mid ? query(ls(k), l, mid, x) : query(rs(k), mid + 1, r, x); } void build(int k, int l, int r) { val[k] = l == r ? 3 : 1; if (l != r) { int mid = (l + r) >> 1; build(ls(k), l, mid); build(rs(k), mid + 1, r); } } int main() { freopen("zoo.in", "r", stdin); freopen("zoo.out", "w", stdout); std::ios::sync_with_stdio(0); cin >> n >> m; for (int i = 1; i <= n; ++i) { f[i] = i; } int tot = n; for (int i = 1; i <= m; ++i) { cin >> op[i] >> x[i]; if (op[i] == 1) { cin >> y[i]; ++tot; f[tot] = tot; v[tot].push_back(find(x[i])); v[tot].push_back(find(y[i])); f[find(x[i])] = tot; f[find(y[i])] = tot; } } for (int i = 1; i <= tot; ++i) { if (i == f[i]) { v[0].push_back(i); } } dfs(0); memset(f, 0, sizeof f); for (int i = 1; i <= n; ++i) { f[i] = i; sz[i] = 1; } tot = n; build(1, 1, dfc); for (int i = 1; i <= m; ++i) { if (op[i] == 1) { int _x = find(x[i]), _y = find(y[i]); modify(1, 1, dfc, st[_x], ed[_x], 2 * ksm(3, sz[_y] - 1) % mod); modify(1, 1, dfc, st[_y], ed[_y], ksm(3, sz[_x] - 1)); ++tot; f[_x] = f[_y] = f[tot] = tot; sz[tot] = sz[_x] + sz[_y]; } else { cout << (long long)query(1, 1, dfc, st[x[i]]) * ksm(3, n - sz[find(x[i])]) % mod << '\n'; } } return 0; }
序列修改
感覺也很厲害。
首先手玩一下發現只可能修改每個值第一次出現的位置,不妨設我們修改的位置是 \(x\),那麼肯定要修改成另外的一個值 \(a_y\), \(y\) 也是第一次出現。
考慮 \(y\) 和 \(x\) 的大小關係,如果在前邊的話,收益就是 \(|a_x-a_y|-S_x+S_y\)。
否則的話,討論一下應該有 \(y<z\),收益就是 \(|a_x-a_y|-S_y+S_z\)。這個東西可以根據 \(a_x\) 和 \(a_y\) 的大小關係用資料結構維護。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f, N = 5e5 + 5;
int n, m, a[N], b[N], nxt[N];
long long sum[N], ans, del;
bool vis[N];
set<int> s;
map<int, int> lst;
struct BIT {
long long c[N];
BIT() { memset(c, -63, sizeof c); }
void add(int x, long long v) {
for (int i = x; i <= n; i += i & -i) {
c[i] = max(c[i], v);
}
}
long long query(int x) {
long long ret = -INF;
for (int i = x; i; i -= i & -i) {
ret = max(ret, c[i]);
}
return ret;
}
} tr1, tr2;
int main() {
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
scanf("%d", &n);
for (int i = n; i; i--) {
sum[i] = sum[i + 1] + i;
}
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (!lst[a[i]]) {
ans += sum[i];
vis[i] = 1;
b[++m] = a[i];
} else {
nxt[lst[a[i]]] = i;
}
lst[a[i]] = i;
nxt[i] = n + 1;
}
sort(b + 1, b + m + 1);
s.insert(-INF);
s.insert(INF);
for (int i = 1; i <= n; i++) {
if (vis[i]) {
set<int>::iterator it = s.lower_bound(a[i]);
del = min(del, -sum[i] + sum[nxt[i]] + *it - a[i]);
del = min(del, -sum[i] + sum[nxt[i]] - *(--it) + a[i]);
s.insert(a[i]);
}
}
for (int i = n; i; i--) {
if (vis[i]) {
int pos = lower_bound(b + 1, b + m + 1, a[i]) - b;
del = min(del, sum[nxt[i]] + a[i] - tr1.query(pos));
del = min(del, sum[nxt[i]] - a[i] - tr2.query(n - pos + 1));
tr1.add(pos, sum[i] + a[i]);
tr2.add(n - pos + 1, sum[i] - a[i]);
}
}
printf("%lld", ans + del);
return 0;
}
擺放鞋子
考慮每個鞋子會有一個權值,左上右下為0123,那麼總是可以在總權值模四不變的情況下對於圖做調整。
考慮直接根據左右鞋子的關係跑出來一個二分圖匹配。
然後如果設答案是 \(ans\)。在不是完美匹配的情況下,答案就是 \(ans\)。否則,我們看看匹配之後的圖是不是和之前的權值和一樣,然後不一樣就是搞一個答案減一。