Problem T1. 雷神领域
生成新的点的性质是两个坐标都已经配对过,也就是在同一个连通块中。
考虑并查集维护,每次将。
时间复杂度 (mathcal O(n^2))
#include <bits/stdc++.h>
using namespace std;
const int _max = 5000;
int fa[10005];
int dp[5005][5005];
int n;
int get(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= 10000; ++i)
fa[i] = i;
for (int i = 1, x, y; i <= n; ++i) {
cin >> x >> y;
int p = get(x), q = get(y + _max);
if (p != q) {
fa[p] = q;
}
}
dp[0][0] = 0;
for (int i = 1; i <= _max; ++i)
for (int j = 1; j <= _max; ++j)
if (get(i) == get(j + _max)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
cout << dp[_max][_max] << '
';
return 0;
}
Problem T2. 密码锁
和启智树暑假集训的一道题相似,考虑对于原序列每一个元素和前一个元素异或一下,维护操作后的序列。
对于每两个相邻的 (1) 之间的点必须要进行奇数次操作。
因为需要精准覆盖,可以看做一个图,用广搜预处理出从一个点开始,把一堆连续的点变成合法的最小步数。
然后只剩下 (2k) 个点,可以状压 DP。
令 (dp[mask]) 表示当前已经处理好的状态为 (mask) 的最小步数。
每次要覆盖两个节点,所以 (dp[mask]=Min{dp[last]+move[i][j]})
时间复杂度 (mathcal O(2k imes m+2^{20}2k))
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void read(T& x) {
x = 0;
int c = 0, f = 0;
for (; !isdigit(c); c = getchar()) f |= c == '-';
for (; isdigit(c); c = getchar()) x = x * 10 + (c & 15);
x = f ? -x : x;
}
const int N = 1e4 + 5, M = 2e6 + 5;
int a[N], x[N], num[N], dis[N], s[N], d[25][25];
int dp[M], vis[M];
int n, m, k, cnt;
void spfa(int x) {
queue<int> q;
memset(vis, 0, sizeof vis);
q.push(x);
vis[x] = 1;
dis[x] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 1; i <= m; ++i) {
if (u + s[i] <= n && !vis[u + s[i]]) {
vis[u + s[i]] = 1;
dis[u + s[i]] = dis[u] + 1;
q.push(u + s[i]);
}
if (u - s[i] > 0 && !vis[u - s[i]]) {
vis[u - s[i]] = 1;
dis[u - s[i]] = dis[u] + 1;
q.push(u - s[i]);
}
}
}
for (int i = 1; i <= n; ++i)
if (num[i])
if (!vis[i])
d[num[x]][num[i]] = 0x3f3f3f3f;
else
d[num[x]][num[i]] = dis[i];
}
int dfs(int mask) {
if (!mask)
return 0;
if (vis[mask])
return dp[mask];
vis[mask] = 1, dp[mask] = 0x3f3f3f3f;
int s = 0;
for (int i = 1; i <= cnt; ++i) {
if (mask >> (i - 1) & 1) {
if (!s)
s = i;
else {
if (d[s][i] != 0x3f3f3f3f)
dp[mask] = min(dp[mask], dfs(mask ^ (1 << (s - 1)) ^ (1 << (i - 1))) + d[s][i]);
}
}
}
return dp[mask];
}
int main() {
freopen("password.in", "r", stdin);
freopen("password.out", "w", stdout);
read(n), read(k), read(m);
for (int i = 1; i <= k; ++i) {
read(x[i]);
a[x[i]] = 1;
}
for (int i = 1; i <= m; ++i)
read(s[i]);
++n;
for (int i = n; i >= 1; --i)
a[i] ^= a[i - 1];
for (int i = 1; i <= n; ++i)
if (a[i])
num[i] = ++cnt;
for (int i = 1; i <= n; ++i)
if (a[i])
spfa(i);
memset(vis, 0, sizeof vis);
dfs((1 << cnt) - 1);
if (dp[(1 << cnt) - 1] == 0x3f3f3f3f)
printf("-1
");
else
printf("%d
", dp[(1 << cnt) - 1]);
return 0;
}
Problem T3. 花园
考虑维护从根节点开始到某一个节点的某个颜色的答案,这样就可以变成子树修改,单点查询。
时间复杂度 (mathcal O(n+qlog n))
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void read(T& x) {
x = 0;
int c = 0, f = 0;
for (; !isdigit(c); c = getchar()) f |= c == '-';
for (; isdigit(c); c = getchar()) x = x * 10 + (c & 15);
x = f ? -x : x;
}
const int N = 1e5 + 5;
struct edge {
int v, nxt;
} e[N << 1];
int head[N];
int edge_cnt;
void add_edge(int u, int v) {
e[++edge_cnt] = (edge){v, head[u]};
head[u] = edge_cnt;
}
int n, q;
int fa[N], sz[N], top[N], son[N], dep[N], dfn[N];
int dfc, color_cnt = 1;
map<int, int> disc;
int root[N * 3], col[N];
void Dfs(int u, int father) {
son[u] = 0;
dep[u] = dep[father] + 1;
fa[u] = father;
sz[u] = 1;
for (int i = head[u]; i; i = e[i].nxt) {
if (e[i].v != father) {
Dfs(e[i].v, u);
sz[u] += sz[e[i].v];
if (sz[e[i].v] > sz[son[u]])
son[u] = e[i].v;
}
}
}
void dfs(int u, int tp) {
top[u] = tp, dfn[u] = ++dfc;
if (!son[u])
return;
dfs(son[u], tp);
for (int i = head[u]; i; i = e[i].nxt)
if (e[i].v != fa[u] && e[i].v != son[u])
dfs(e[i].v, e[i].v);
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
struct node {
int ch[2];
int val;
int tag;
} seg[N * 100];
int node_cnt = 0;
void pushdown(int p, int l, int r) {
if (seg[p].tag == 0 || l == r)
return;
if (seg[p].ch[0] == 0)
seg[p].ch[0] = ++node_cnt;
if (seg[p].ch[1] == 0)
seg[p].ch[1] = ++node_cnt;
seg[seg[p].ch[0]].tag += seg[p].tag;
seg[seg[p].ch[1]].tag += seg[p].tag;
seg[seg[p].ch[0]].val += seg[p].tag;
seg[seg[p].ch[1]].val += seg[p].tag;
seg[p].tag = 0;
}
void modify(int& p, int l, int r, int ql, int qr, int v) {
if (!p)
p = ++node_cnt;
if (ql <= l && r <= qr) {
seg[p].tag += v;
seg[p].val += v;
return;
}
pushdown(p, l, r);
int mid = l + r >> 1;
if (ql <= mid)
modify(seg[p].ch[0], l, mid, ql, qr, v);
if (qr > mid)
modify(seg[p].ch[1], mid + 1, r, ql, qr, v);
}
int query(int p, int l, int r, int pos) {
if (!p)
return 0;
if (l == r)
return seg[p].val;
pushdown(p, l, r);
int mid = l + r >> 1;
if (pos <= mid)
return query(seg[p].ch[0], l, mid, pos);
else
return query(seg[p].ch[1], mid + 1, r, pos);
}
int main() {
read(n), read(q);
for (int i = 1; i <= n; ++i) {
int t;
read(t);
if (!disc.count(t))
disc[t] = ++color_cnt;
col[i] = disc[t];
}
for (int i = 1; i < n; ++i) {
int u, v;
read(u), read(v);
add_edge(u, v);
add_edge(v, u);
}
Dfs(1, 0);
dfs(1, 1);
for (int i = 1; i <= n; ++i)
modify(root[col[i]], 1, n, dfn[i], dfn[i] + sz[i] - 1, 1);
char opt[5];
int x, y, t;
for (int qt = 1, last_ans = 0; qt <= q; ++qt) {
scanf("%s", opt);
if (opt[0] == 'C') {
read(x), read(t);
x ^= last_ans, t ^= last_ans;
if (!disc.count(t))
disc[t] = ++color_cnt;
t = disc[t];
modify(root[col[x]], 1, n, dfn[x], dfn[x] + sz[x] - 1, -1);
modify(root[t], 1, n, dfn[x], dfn[x] + sz[x] - 1, 1);
col[x] = t;
} else {
read(x), read(y), read(t);
x ^= last_ans, y ^= last_ans, t ^= last_ans;
if (!disc.count(t))
disc[t] = ++color_cnt;
t = disc[t];
int _lca = lca(x, y), ans;
ans = query(root[t], 1, n, dfn[x]) + query(root[t], 1, n, dfn[y]);
ans -= 2 * query(root[t], 1, n, dfn[_lca]);
if (col[_lca] == t)
++ans;
printf("%d
", last_ans = ans);
}
}
return 0;
}