牛客多校第七场题解
xay loves connected graphs
https://ac.nowcoder.com/acm/contest/11258/A
B - xay loves monotonicity
题意:
给你两个序列,其中第二个为序列。每次单点修改第一个序列;区间修改第二个序列,让区间所有数字
;进行区间询问。
询问的规则是,从左端点开始,往右找第一个大于等于它的值的位置,然后接着找,直到不能找为止。然后把这些位置的数组对应的值取出来,如果相邻的值不相同,那么就会有一个贡献。输出贡献和。
思路:
洛谷上有原题,先丢个链接P4198
当时写完原题,我就觉得原题会写,这道题就一定会写,只是多了些东西恶心你。
然后我就被卡了
本人习惯做线段树pushUp,合并两个区间维护时回传结构体,然后就写得很麻烦。这里传个指针就很简单了。
先写原题,再写这题就很容易理解了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const int N = 2e5 + 7; int n, m, id[N]; int a[N], b[N]; struct Q { int opt, ql, qr; }q[N]; #define ls (id << 1) #define rs (id << 1 | 1) #define mid (l + r >> 1) struct Seg { int mx[N << 2], t[N << 2], bt[N << 2], lazy[N << 2]; void down(int id) { if (!lazy[id]) return; lazy[ls] ^= 1, bt[ls] ^= 1; lazy[rs] ^= 1, bt[rs] ^= 1; lazy[id] ^= 1; } int query(int id, int l, int r, int& MAX, int& bit) { if (mx[id] < MAX) return 0; if (l == r) { int res = bt[id] ^ bit; if (MAX == -1) res = 0; // 说明我以当前节点为开头,所以肯定不能算贡献 MAX = mx[id]; bit = bt[id]; return res; } down(id); if (mx[ls] >= MAX) { int res = query(ls, l, mid, MAX, bit); // 注意修改MAX和bit,因为当前这个区间整体是有贡献的,所以要用这个区间的值进行修改 MAX = mx[id]; bit = bt[id]; return t[id] - t[ls] + res; } else { return query(rs, mid + 1, r, MAX, bit); } } void up(int id, int l, int r) { mx[id] = mx[ls], bt[id] = bt[ls]; // 先把左儿子的值传上来,再用左儿子的信息去找有儿子,维护区间整体信息 t[id] = t[ls] + query(rs, mid + 1, r, mx[id], bt[id]); } void build(int id, int l, int r) { mx[id] = t[id] = bt[id] = 0; if (l == r) { mx[id] = a[l]; bt[id] = b[l]; return; } build(ls, l, mid); build(rs, mid + 1, r); up(id, l, r); } void modify(int id, int l, int r, int p, int v) { if (l == r) { mx[id] = v; return; } down(id); if (p <= mid) modify(ls, l, mid, p, v); else modify(rs, mid + 1, r, p, v); up(id, l, r); } void flip(int id, int l, int r, int ql, int qr) { if (l >= ql && r <= qr) { bt[id] ^= 1; lazy[id] ^= 1; return; } down(id); if (ql <= mid) flip(ls, l, mid, ql, qr); if (qr > mid) flip(rs, mid + 1, r, ql, qr); up(id, l, r); } int getAns(int id, int l, int r, int ql, int qr, int &MAX, int &bit) { if (mx[id] < MAX) return 0; if (l >= ql && r <= qr) { return query(id, l, r, MAX, bit); } down(id); int res = 0; // 优先询问左儿子,符合询问要求,所以可以直接写 if (ql <= mid) res += getAns(ls, l, mid, ql, qr, MAX, bit); if (qr > mid) res += getAns(rs, mid + 1, r, ql, qr, MAX, bit); return res; } }seg; void solve() { cin >> n; for (int i = 1; i <= n; ++i) { cin >> a[i]; } for (int i = 1; i <= n; ++i) { cin >> b[i]; } cin >> m; for (int i = 1; i <= m; ++i) { cin >> q[i].opt >> q[i].ql >> q[i].qr; } seg.build(1, 1, n); for (int i = 1; i <= m; ++i) { if (q[i].opt == 1) seg.modify(1, 1, n, q[i].ql, q[i].qr); else if (q[i].opt == 2) seg.flip(1, 1, n, q[i].ql, q[i].qr); else { int mx = -1, bt = 0; cout << seg.getAns(1, 1, n, q[i].ql, q[i].qr, mx, bt) << '\n'; } } } int main() { #ifndef LOCAL ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr); cout << fixed << setprecision(20); #endif int t = 1; while (t--) solve(); return 0; }
F - xay loves trees
题意:
给你两棵树,在第一棵树上找一条最长链,要求链上所有点的在第二棵树上互相之间没有祖先关系。
思路:
点是点
的祖先,当且仅当
在以
为根节点的子树中。由此可见,当我们选了一个节点后,它的子树中的所有节点都不能选了。
因为要保持是第一棵树上的一条链,所以我们选择在第一棵树上进行dfs,过程中把当前节点在第二棵树中形成的子树中的所有节点的权值加,当前长链是合法的当且仅当所有节点权值的最大值为
。
由于要在第一棵树上连续,所以可以使用滑动窗口,当添加一个点会让当前状态非法时,就一直把前面的点删掉,以当前状态进行,回来之后再加回来,继续下一个递归。但是这样的做***被卡到
,但是现在还没有加能卡掉这个做法的数据。
注意到我们只求最长的长度,且当前窗口长度是之前求出来的最长的合法长度,所以我们可以让这个窗口长度只增不降,即每次最多删除一个节点,这样就可以在的时间复杂度下找到最长的答案。
#include <bits/stdc++.h> using namespace std; #define endl "\n" inline int rd() { int f = 0; int x = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) f |= (ch == '-'); for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0'; if (f) x = -x; return x; } typedef long long ll; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const int N = 3e5 + 7; vector<int> v1[N], v2[N]; int n, stt[N], fnt[N], dfsId; int ans, sz; int que[N], head, rear; #define ls (id << 1) #define rs (id << 1 | 1) #define mid (l + r >> 1) struct Seg { int t[N << 2], lazy[N << 2]; void up(int id) { t[id] = max(t[ls], t[rs]); } void down(int id) { if (!lazy[id]) return; int tmp = lazy[id]; lazy[ls] += tmp; lazy[rs] += tmp; t[ls] += tmp; t[rs] += tmp; lazy[id] = 0; } void build(int id, int l, int r) { t[id] = 0, lazy[id] = 0; if (l == r) return; build(ls, l, mid); build(rs, mid + 1, r); } void modify(int id, int l, int r, int ql, int qr, int v) { if (l >= ql && r <= qr) { t[id] += v; lazy[id] += v; return; } down(id); if (ql <= mid) modify(ls, l, mid, ql, qr, v); if (qr > mid) modify(rs, mid + 1, r, ql, qr, v); up(id); } int query(int id, int l, int r, int ql, int qr) { // dbg(id, l, r); if (l >= ql && r <= qr) return t[id]; down(id); int res = -inf; if (ql <= mid) res = max(res, query(ls, l, mid, ql, qr)); if (qr > mid) res = max(res, query(rs, mid + 1, r, ql, qr)); return res; } }seg; void dfsOd(int u, int fa) { stt[u] = ++dfsId; for (auto v : v2[u]) if (v != fa) dfsOd(v, u); fnt[u] = dfsId; } void dfs(int u, int fa) { seg.modify(1, 1, n, stt[u], fnt[u], 1); que[rear++] = u; int tmp = seg.query(1, 1, n, 1, n); if (tmp >= 2) { seg.modify(1, 1, n, stt[que[head]], fnt[que[head]], -1); head++; } ans = max(ans, rear - head); for (auto v : v1[u]) if (v != fa) dfs(v, u); seg.modify(1, 1, n, stt[u], fnt[u], -1); rear--; if (tmp >= 2) { head--; seg.modify(1, 1, n, stt[que[head]], fnt[que[head]], 1); } } void solve() { cin >> n; for (int i = 1; i <= n; ++i) { v1[i].clear(); v2[i].clear(); } for (int i = 2, u, v; i <= n; ++i) { cin >> u >> v; v1[u].push_back(v); v1[v].push_back(u); } for (int i = 2, u, v; i <= n; ++i) { cin >> u >> v; v2[u].push_back(v); v2[v].push_back(u); } dfsId = 0; dfsOd(1, 0); ans = 0; head = rear = 0; seg.build(1, 1, dfsId); dfs(1, 0); cout << ans << endl; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr); cout << fixed << setprecision(20); int t = 1; cin >> t; while (t--) solve(); return 0; }
H - xay loves count
题意:
给定序列,求三元组
个数,满足
。
思路:
记录每个数字出现的数量,枚举其中两个数,就可以算出第三个数,把它们的数量乘起来就是对答案的贡献。枚举时注意跳过不合法的数。
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define dbg(x...) do{ cerr << #x << " -> "; err(x);} while (0) void err() { cerr << endl;} template<class T, class... Ts> void err(const T& arg, const Ts&... args) { cerr << arg << " "; err(args...);} const int N = 1e6 + 7; int n; int a[N]; ll num[N]; ll f[N]; void run() { scanf("%d", &n); for (int i =1 ; i <= n; ++i) { scanf("%d", &a[i]); num[a[i]]++; } ll ans = 0; for (int i = 1; i < N; ++i) { for (int j = i; j < N; j += i) { ans += num[j] * num[j / i] * num[i]; } } printf("%lld\n", ans); } int main() { int t = 1; while (t--) run(); return 0; }
I - xay loves or
思路:
签到。注意特判不能计入答案。
#include <bits/stdc++.h> using namespace std; typedef long long ll; //#define endl "\n" #define dbg(x...) do{ cerr << #x << " -> "; err(x);} while (0) void err() { cerr << endl;} template<class T, class... Ts> void err(const T& arg, const Ts&... args) { cerr << arg << " "; err(args...);} const int maxn = 1e5 + 7; void run() { int x, s; cin >> x >> s; if (((x ^ s) | s) > s) { cout << 0 << endl; return; } int y = x ^ s; y ^= s; cout << (1 << __builtin_popcount(y)) - ((x ^ s) == 0) << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t = 1; while (t--) run(); return 0; }
J - xay loves Floyd
题意:
编程时把Floyd算法写错了,枚举中间点的for放在了第三维,问这样做了之后求出来的两点距离,还有多少个是正确的。定义。
思路:
放在第三维之后,相当于两点之间的最短路最多只能由一个点更新。考虑何种情况下这样子求得的最短路仍然是正确的。
1.两点直接相连的边就是最短路
2.两点,
通过一个点
相连的最短路是正确的最短路,且
->
,
->
的结果正确,且
在
->
的最短路径上。
两点之间正确的最短路可以对每个点跑一遍,然后比较跑出来的图和原图相等的地方,这些就是属于第一种情况的,把他们标记一下,计入答案。
对于第二种情况,我们先枚举两边的点,
,再枚举中间点
,按照上述方法直接搞。这样的时间和空间复杂度都不太行,考虑优化。
首先解决路径上的点的问题。我们枚举,
之后,要取出
->
最短路径上的点,每次重新取不合理,预处理又存不下。尝试能否固定其中一维,得到递推式。
因为跑过一遍,我们能清楚地知道
当前到所有点的最短距离。那么我们可以来模拟一下
的最后一步的转移过程。即枚举点
的时候,按照点
到点
的距离,从小到大枚举,然后遍历从
出发的边,设边权为
,入点为
,判断点
到点
的距离是否要用点
到点
的这条边更新,然后就可以把点
到点
的最短路径经过的点转移到点
到点
上面。再看一眼
,最多开个
大小的东西,这种转移又很像二进制的或操作,那么我们就可以开个长度为
的
维
,表示点
到点
之间要经过哪些点,这个转移就比较容易了。
既然都用到了,看看能否用
优化检查
->
,
->
的正确性。继续开两个二维
,一个表示点
到哪些点的最短路是能通过
次转移获得正确答案的,一个表示哪些点到点
的最短路是能通过
次转移获得正确答案的。那么我们对这两个
交一下,就能得到所有可行的中间点。
接下来我们要做的,就是检查这些可行的中间点,有没有在点到点
的最短路径上的。我们只需要让之前得到的记录路径上的点的
再和它交一下就行了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const int N = 2e3 + 7; int dis[N][N], disF[N][N]; int n, m; vector< pair<int, int> > G[N]; bitset<N> toR[N], toL[N], pot[N]; void dij(int s){ for (int i = 1; i <= n; ++i) dis[s][i] = inf; dis[s][s] = 0; priority_queue< pair<int, int>, vector< pair<int, int> >, greater< pair<int, int> > > q; q.push({0, s}); while (!q.empty()) { int u = q.top().second; q.pop(); for (auto t : G[u]) { int v = t.second, w = t.first; if (dis[s][v] > dis[s][u] + w) { dis[s][v] = dis[s][u] + w; q.push({dis[s][v], v}); } } } } void solve() { memset(disF, 0x3f,sizeof(disF)); scanf("%d %d", &n, &m); for (int i = 1, u, v, w; i <= m; ++i) { scanf("%d %d %d", &u, &v, &w); G[u].push_back({w, v}); disF[u][v] = w; } for (int i = 1; i <= n; ++i) dij(i); for (int i = 1; i <= n; ++i) disF[i][i] = 0; for (int u = 1; u <= n; ++u) { for (int v = 1; v <= n; ++v) { if (dis[u][v] == disF[u][v]) { toR[v][u] = 1; // v作为终点或者中间点,哪些点能到v toL[u][v] = 1; // u作为起始点,能到哪些点 } } } vector<int> que(n); iota(que.begin(), que.end(), 1); for (int u = 1; u <= n; ++u) { sort(que.begin(), que.end(), [&](int x, int y){ return dis[u][x] < dis[u][y]; }); int pre = u; for (int i = 1; i <= n; ++i) pot[i].reset(), pot[i][i] = 1; for (auto v : que) { for (auto t : G[v]) { if (dis[u][v] + t.first == dis[u][t.second]) pot[t.second] |= pot[v]; // 使用当前边,最短路径经过点可以转移 } } for (int v = 1; v <= n; ++v) { auto tmp = toR[v] & toL[u] & pot[v]; if (tmp.count()) { toL[u][v] = 1, toR[v][u] = 1; } } } int ans = 0; for (int i = 1; i <= n; ++i) { ans += toR[i].count(); } printf("%d\n", ans); } int main() { int t = 1; while (t--) solve(); return 0; }
K - xay loves sequence
题意:
定义为让区间所有值都变成
的最小操作次数,每次操作可以选择其中的一段区间,让区间内的每个
或者
。
思路:
先不考虑模,每次要修改一个区间,取差分数组
,令
,该差分数组满足
,即在
范围内,我选择任意一个正数(负数),一定会存在另一个负数(正数)。在没有模
时,要使得区间都为
,每次让区间同时加(减)
,相当于选两个位置,一个加上(减少)
,另一个减少(加上)
,所以答案是$
$
在模意义下,
,对一段区间增加
或者减少
是不需要算贡献的,即我可以在差分数组
范围内选择两个位置,一个位置加上
,另一个位置减少
。考虑这样子的操作如何会让答案更优。
- 对于加
来说,肯定要选择
的地方,否则肯定不优。增加后
变成了
,
- 对于减
来说,肯定要选择
的地方,否则肯定不优。减少后
变成了
,
这两个东西肯定是同时选的,如果只选择一个的话,一个让答案增加了,另一个让答案增加了
,无论
怎么取值都到不了
,肯定是不划算的。
现在的问题就转换成了求两者的之和最小是多少。我们可以对它们分别进行排序,求一个前缀和
即可。这样子做的时间复杂度显然不能接受。观察到两者的
之和是一个单峰函数,我们可以在
级别下求出下凸峰值,但这样还是不够。
考虑二分后优化查询,我们要对一个区间求前大的
和,这个东西就可以用主席树来求解了。两颗主席树用来维护正负差分值的绝对值,查询一个区间时,要注意头尾的差分值的绝对值并没有加进来,要额外传递一下
和
。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const int N = 2e5 + 7; const int MAX = (1 << 30) - 1; int n, q, ql, qr, k; ll a[N], pre[N]; struct Seg { struct Node { int ls, rs, cnt; ll sum; }t[N * 50]; int rt[N], tot; void modify(int &rt, int l, int r, int pos, int v) { int now = ++tot; t[now] = t[rt]; t[now].sum += v; t[now].cnt += 1; if (l == r) { rt = now; return; } int mid = (l + r) >> 1; if (pos <= mid) modify(t[now].ls, l, mid, pos, v); else modify(t[now].rs, mid + 1, r, pos, v); rt = now; } ll query(int lr, int rr, int l, int r, int k, ll ed) { if (!k) return 0; if (l == r) { // 查询到单点,还剩下k个数字没放进来 return (ll)l * k; } int mid = (l + r) >> 1; int nums = t[ t[rr].rs ].cnt - t[ t[lr].rs ].cnt + (ed > mid && ed <= r); // 计入传递进来的差分值的绝对值的影响 // 这里有个trick,判断 nums <= k 而不是 nums < k,这样右边就不用再分区间了,能减少很多递归。这题大概能减个1s左右 if (nums <= k) return query(t[lr].ls, t[rr].ls, l, mid, k - nums, ed) + t[ t[rr].rs ].sum - t[ t[lr].rs ].sum + (ed > mid && ed <= r) * ed; else return query(t[lr].rs, t[rr].rs, mid + 1, r, k, ed); } int getsz(int l, int r) { return t[ rt[r] ].cnt - t[ rt[l] ].cnt + 1; // 注意还没传递进来的差分值的绝对值也要占一个位置 } }seg[2]; ll calc(int l, int r, int kk) { ll res = (pre[r] - pre[l] + a[l] + a[r]) / 2; return res + (ll)kk * k - seg[0].query(seg[0].rt[l], seg[0].rt[r], 0, MAX, kk, a[r]) - seg[1].query(seg[1].rt[l], seg[1].rt[r], 0, MAX, kk, a[l]); } void solve() { cin >> n >> q; for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 2; i <= n; ++i) { ll v = abs(a[i] - a[i - 1]); pre[i] = pre[i - 1] + v; seg[1].rt[i] = seg[1].rt[i - 1]; seg[0].rt[i] = seg[0].rt[i - 1]; if (a[i] >= a[i - 1]) { seg[1].modify(seg[1].rt[i], 0, MAX, v, v); } else { seg[0].modify(seg[0].rt[i], 0, MAX, v, v); } } while (q--) { cin >> ql >> qr >> k; int l = 0, r = min(seg[0].getsz(ql, qr), seg[1].getsz(ql, qr)), mid; while (l <= r) { mid = (l + r) >> 1; if (!mid || calc(ql, qr, mid) < calc(ql, qr, mid - 1)) l = mid + 1; else r = mid - 1; } cout << calc(ql, qr, l - 1) << '\n'; } } int main() { #ifndef LOCAL ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr); cout << fixed << setprecision(20); #endif int t = 1; while (t--) solve(); return 0; }