2020牛客暑期多校训练营(第五场)
题意:
给你一个树, 每个边都有个权值, 你有以下几种操作。
- 选择两个点 两一条边 使边权为x, 且 这两个点所连的边形成的环所有边权异或和为0.
- 可以环中删掉任意一条边。
问最后所有边权之和最小是多少?
题解:
如果你知道一个知识点叫做异或最小生成树, 那么这题就很好写了, 如果你不知道基本上写不出来。
什么是异或最小生成树?
给你 n个点 每个点都有权值, 这n个点可以两两连一条边且边权是两个点的异或值, 问再这个完全图中的最小生成树是多少?
异或最小生成树就是来解决这个问题的。具体怎么解决和这个算法的证明网上应该有很好的博客我就不在多提了。
然后这题和最小生成树有什么关系?
给你的是边权。
如果我这个图连接成一个完成图(两两都有边)然后再删掉所有有环的边, 是不是成了最小生成树了?
那怎么连接成完成图, 这题要保证添加的边所形成的环异或和为0.
如果知道每个点的点权, 比如 (\(a[1], a[2], a[3])\)形成了一个环, 那么该环的异或和就是0 (\(a[1] \oplus a[2] \oplus a[2] \oplus a[3] \oplus a[3] \oplus a[1] = 0\))。
如果知道任意一点的权值, 因为有了边权 其它所有点的权值都能求出来。
如果知道任意一点的权值呢?
我们可以随便给一个点设一个任意的权值。这样就可以求出所有点的值了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
vector<pair<int, ll>>g[N];
int tree[40 * N][2], n, m;
vector<ll>v;
void dfs(int u, ll w, int fa) {
v.push_back(w);
for (auto it: g[u]) {
int to = it.first;
ll cost = it.second;
if (to == fa) continue;
dfs(to, w ^ cost, u);
}
}
int top = 2;
void insert(ll x) {
int rt = 1;
for (int i = 29; i >= 0; i--) {
if (x & (1 << i)) {
if (tree[rt][1] == 0) {
tree[rt][1] = top++;
}
rt = tree[rt][1];
} else {
if (tree[rt][0] == 0) {
tree[rt][0] = top++;
}
rt = tree[rt][0];
}
}
}
ll find(ll value, ll sum, int rt, int p) {
if ((value & (1 << p)) && tree[rt][1]) {
return find(value, sum, tree[rt][1], p - 1);
} else if ((value & (1 << p)) == 0 && tree[rt][0]) {
return find(value, sum, tree[rt][0], p - 1);
} else if(tree[rt][0]) {
return find(value, sum, tree[rt][0], p - 1) + (1ll << p);
} else if (tree[rt][1]) {
return find(value, sum, tree[rt][1], p - 1) + (1ll << p);
}
return sum;
}
ll Ans = 0;
void work(ll sum, int p, int node) {
if (p < 0) return;
int r = sum + (1 << (p + 1)) - 1;
int l = sum + (1 << p);
l = lower_bound(v.begin(), v.end(), l) - v.begin();
r = upper_bound(v.begin(), v.end(), r) - v.begin();
ll minn = LLONG_MAX;
for (int i = l; i < r; i++) {
ll cnt = 0;
if ((v[i] & (1 << p))) {
cnt = (1 << p);
}
if (tree[node][0]) {
minn = min(minn, find(v[i], cnt, tree[node][0], p - 1));
}
}
if (minn != LLONG_MAX) {
Ans += minn;
}
if (tree[node][0]) {
work(sum, p - 1, tree[node][0]);
}
if (tree[node][1]) {
work(sum + (1 << p), p - 1, tree[node][1]);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
u++, v++;
g[u].push_back({v, 1ll*w});
g[v].push_back({u, 1ll*w});
}
dfs(1, 2123, 0);
sort(v.begin(), v.end());
for (ll i: v) {
insert(i);
}
work(0, 29, 1);
printf("%lld\n", Ans);
}
题意:
给你个数组p长度为n且每个元素都 不同且p属于[1, n], 然后你可以进行下面的两种操作:
- 将\(p[1], p[2], p[3]......p[n - 1], p[n]\) 变成 \(p[n - 1], p[1], p[2], p[3]......p[n]\)
- 将 \(p[1], p[2], p[3]......p[n - 1], p[n]\) 变成 \(p[2], p[3]......p[n - 1], p[n], p[1]\)
每次连续操作1都会花费一元, 操作2不花钱, 问花费最少的前是p 总小到大排序。
题解:
操作1是将前 n -1个数进行转圈, 而操作2是将 n个数进行转圈, 而同时使用操作1, 与操作2,花费1元可以使
前 n - 1个某个位置与最后一个交换, 也就是说那些上升的位置会一直不变, 所有就是求最长的lcs就行了。
#include<bits/stdc++.h>
using namespace std;
int p[1007], n, a[507];
int work(int l, int r) {
int tot = 1;
for (int i = l; i <= r; i++) {
if (tot == 1) {
a[tot++] = p[i];
} else {
int pos = lower_bound(a + 1, a + tot, p[i]) - a;
if (pos < tot) {
a[pos] = p[i];
} else {
a[tot++] = p[i];
}
}
}
return tot - 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
p[n + i] = p[i] ;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, work(i, i + n - 1));
}
printf("%d\n", n - ans);
}
题意:
给你一个数组a长度为n, 然后有q次询问, 每次询问让你求出区间(l,r)所有子串的&和。
题解:
先来想一下暴力的写法怎么写?
如果每次询问给你一个 (l, r)那么暴力的写法就是先固定一个右端点, 然后枚举左端点, 把每次枚举的区间&的值丢进一个set里面最后输出set的大小。
复杂度应该是 q * n *n *log(n)肯定不行。
我们仔细发现,当固定右端点时枚举左端点(从大到小枚举)会发现区间的&值会一直减小, 进一步发现最多只要30种(也就是二进制的位数)。
如果我们可以预处里
每个r为右端点, 设x为某一种树 l为 区间 l 到 r 的& 为 x 且 l是最小值。
这样 我每个端点再那个区间可以&出那些数就知道了。
如何记录有多少种呢?
用主席树, 主席树查询区间不同种类数的方法来写。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int n, a[N];
#define m (l + r) / 2
#define lson 2 * node
#define rson 2 * node + 1
struct hjt{
int sum, l, r;
}tree[150 * N];
struct segement{
int tr[4 * N];
void build(int l, int r, int node) {
if (l == r) {
tr[node] = a[l];
return;
}
build(l, m, lson);
build(m + 1, r, rson);
tr[node] = tr[lson] & tr[rson];
}
int query(int ql, int qr, int l, int r, int node) {
if (ql <= l && qr >= r) {
return tr[node];
}
int ans = INT_MAX;
if (ql <= m) ans = ans & query(ql, qr, l, m, lson);
if (qr > m) ans = ans & query(ql, qr, m + 1, r, rson);
return ans;
}
};
int top = 1, rt[N];
void update(int v, int pos, int last, int &now, int l, int r) {
now = top++;
tree[now] = tree[last];
if (l == r) {
tree[now].sum += v;
return;
}
if (pos <= m) update(v, pos, tree[last].l, tree[now].l, l, m);
else update(v, pos, tree[last].r, tree[now].r, m + 1, r);
tree[now].sum = tree[tree[now].l].sum + tree[tree[now].r].sum;
}
int query(int ql, int qr, int now, int l, int r) {
if (ql <= l && qr >= r) {
return tree[now].sum;
}
int ans = 0;
if (ql <= m) ans += query(ql, qr, tree[now].l, l, m);
if (qr > m) ans += query(ql, qr, tree[now].r, m + 1, r);
return ans;
}
unordered_map<int, int>vis;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
segement cnt;
cnt.build(1, n, 1);
for (int i = 1; i <= n; i++) {
if (vis[a[i]] == 0) {
update(1, i, rt[i - 1], rt[i], 1, n);
} else {
int temp;
update(-1, vis[a[i]], rt[i - 1], temp, 1, n);
update(1, i, temp, rt[i], 1, n);
}
vis[a[i]] = i;
int l = 1, r = i, ans = -1, base = a[i];
for (int j = 1; j <= 30; j++) {
ans = -1;
l = 1;
while (l <= r) {
int cat = cnt.query(m, i, 1, n, 1);
if (cat < base) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
r = ans;
if (ans == -1) continue;
base = cnt.query(ans, i, 1, n, 1);
if (vis[base]) {
int temp;
update(-1, vis[base], rt[i], temp, 1, n);
update(1, ans, temp, rt[i], 1, n);
} else {
update(1, ans, rt[i], rt[i], 1, n);
}
vis[base] = ans;
}
}
int q; scanf("%d", &q);
int Ans = 0;
while (q--) {
int l, r; scanf("%d %d", &l, &r);
l = (Ans ^ l) % n + 1;
r = (Ans ^ r) % n + 1;
if (l > r)swap(l, r);
printf("%d\n", Ans = query(l, r, rt[r], 1, n));
}
}