比赛链接:
https://vjudge.net/contest/506331
A - Static Query on Tree
题意:
给定一棵 \(n\) 个节点的树,每个节点只能向根节点走,\(q\) 次询问,每次给出三个集合 \(A, B, C\),分别从 \(A, B, C\) 中取出三个元素 \(x, y, z\),问 \(x -> z, y -> z\) 两条路径都经过的点有几个。
思路:
将路径的方向反一下,即 1 为根,将 \(A\) 和 \(B\) 中的元素向根的路径进行染色,将 \(C\) 的子树所有的节点进行染色,同时含有三种颜色的就是答案。先进行树链剖分,接下来就是区间操作了。
三个集合对树的节点的染色操作通过位或上 1,2,4 来完成,这样子当答案为 7 的时候就是三种颜色都染上了。
为了加快查询的速度,每个节点再存上位与的答案,若答案为 7,那么整个区间都是符合条件的,就不用再往下走了。同时,若位或是 < 7 的,也不用查询,因为它的子节点肯定缺少某种颜色了。
每次查询重复建树,会超时,所以再对节点加入一个标记,如果标记为 1,那么后面会对子节点进行清零,然后再进行相应的操作。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q;
struct node{
int l, r, tag, v1, v2, clear;
}tr[N << 2];
void pushup(int u){
tr[u].v1 = tr[u << 1].v1 & tr[u << 1 | 1].v1;
tr[u].v2 = tr[u << 1].v2 | tr[u << 1 | 1].v2;
}
void pushdown(int u){
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.clear){
left.clear = right.clear = 1;
left.v1 = right.v1 = 0;
left.v2 = right.v2 = 0;
left.tag = right.tag = 0;
root.clear = 0;
}
left.v1 |= root.tag;
left.v2 |= root.tag;
left.tag |= root.tag;
right.v1 |= root.tag;
right.v2 |= root.tag;
right.tag |= root.tag;
root.tag = 0;
}
void build(int u, int l, int r){
tr[u] = {l, r};
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
void update(int u, int l, int r, int k){
if (tr[u].l >= l && tr[u].r <= r){
tr[u].v1 |= k;
tr[u].v2 |= k;
tr[u].tag |= k;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u << 1, l, r, k);
if (r > mid) update(u << 1 | 1, l, r, k);
pushup(u);
}
}
int query(int u, int l, int r){
if (tr[u].l >= l && tr[u].r <= r){
if (tr[u].v1 >= 7) return tr[u].r - tr[u].l + 1;
if (tr[u].l == tr[u].r) return 0;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1, sum = 0;
if (l <= mid && tr[u << 1].v2 >= 7) sum = query(u << 1, l, r);
if (r > mid && tr[u << 1 | 1].v2 >= 7) sum += query(u << 1 | 1, l, r);
return sum;
}
int top[N], son[N], dep[N], parent[N], sz[N], id[N];
vector < vector<int> > e(N);
int cnt = 0;
void dfs1(int u){ //计算子树大小、深度、父亲和重儿子
sz[u] = 1;
dep[u] = dep[parent[u]] + 1;
for (auto v : e[u]){
if (v == parent[u]) continue;
parent[v] = u;
dfs1(v);
sz[u] += sz[v];
if (!son[u] || sz[son[u]] < sz[v]){
son[u] = v;
}
}
}
void dfs2(int u, int up){ //计算链的头部节点
id[u] = ++ cnt;
top[u] = up;
if (son[u]) dfs2(son[u], up);
for (auto v : e[u]){
if (v == parent[u] || v == son[u]) continue;
dfs2(v, v);
}
}
void init(int s){
dfs1(s);
dfs2(s, s);
}
void updateRoot(int u, int v, int k){
while(top[u] != top[v]){
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = parent[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
update(1, id[u], id[v], k);
}
void updateSon(int u, int pos){
update(1, id[u], id[u] + sz[u] - 1, pos);
}
void Clear(){
for (int i = 1; i <= n; i ++ )
e[i].clear();
cnt = 0;
}
void solve(){
cin >> n >> q;
for (int i = 1; i < n; i ++ ){
int r;
cin >> r;
e[r].push_back(i + 1);
}
init(1);
build(1, 1, n);
while(q -- ){
int A, B, C;
cin >> A >> B >> C;
for (int i = 0; i < A; i ++ ){
int u;
cin >> u;
updateRoot(1, u, 1);
}
for (int i = 0; i < B; i ++ ){
int u;
cin >> u;
updateRoot(1, u, 2);
}
for (int i = 0; i < C; i ++ ){
int u;
cin >> u;
updateSon(u, 4);
}
cout << query(1, 1, n) << "\n";
tr[1].clear = 1;
tr[1].v1 = tr[1].v2 = tr[1].tag = 0;
}
Clear();
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
B - C++ to Python
题意:
C++ 语言转为 python。
思路:
简单的签到,直接输出删除了下划线和字母的字符串。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
string s;
cin >> s;
for (int i = 0; i < (LL)s.size(); i ++ )
if (s[i] == '(' || s[i] == ')' || s[i] == '-' || (s[i] >= '0' && s[i] <= '9') || s[i] == ',')
cout << s[i];
cout << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
C - Copy
题意:
给定一个长为 \(n\) 的序列,\(q\) 次操作,操作分两种:
1.将 \([l, r]\) 区间中的数字复制后插入到第 \(r\) 位后面。
2.查询第 \(k\) 位的数字。
最后输出每次查询结果的异或和。
思路:
如果按照给定的顺序操作,每次需要考虑当前这个修改操作对后面产生的影响,会将整个查询区间分成很多份。
所以反过来考虑,将操作储存,离线操作。
当当前这位是修改操作,那么它会让它修改位置后面的所有查询操作每一位都减去 \([r - l + 1]\)。
同时,因为求的是异或和,所以没必要每次都将值记录下来,只用知道某一位的数字用了几次,若偶数次,那么它对答案其实没有产生影响。因此,采用 \(bitset\) 去优化操作。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10;
LL a[N], op[N], l[N], r[N];
bitset <N> f, low, high;
void solve(){
LL n, q;
cin >> n >> q;
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int i = 1; i <= q; i ++ ){
cin >> op[i] >> l[i];
if (op[i] == 1) cin >> r[i];
}
f.reset();
for (int i = q; i >= 1; i -- ){
LL a = l[i], b = r[i];
if (op[i] == 1){
low = f & (~bitset<N>(0) >> (N - b - 1)); //将修改位置前面的位取出
high = f & (~bitset<N>(0) << (b + 1)); //将修改位置之后的位置取出
f = low ^ (high >> (b - a + 1)); //后面的位置向前面移动
}
else{
f[a] = f[a] ^ 1;
}
}
LL ans = 0;
for (int i = 1; i <= n; i ++ )
if (f[i])
ans ^= a[i];
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
G - Snatch Groceries
题意:
有 \(n\) 段区间 \([l, r](l < r)\),表示不同的信号段,按照时间顺序依次发出,当某段区间与之前有重复,重复的信号就不会发射,且停止发射后面的所有信号,问有多少段信号成功发射。
思路:
排序,然后按照题意计算就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL n;
cin >> n;
vector < pair<LL, LL> > range(n);
for (int i = 0; i < n; i ++ )
cin >> range[i].first >> range[i].second;
sort(range.begin(), range.end());
LL ans = 1;
for (int i = 1; i < n; i ++ ){
if (range[i].first <= range[i - 1].second){
ans -- ;
break;
}
ans ++ ;
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
I - ShuanQ
题意:
已知 \(Q = P^{-1}\),\(P * Q \equiv 1 mod M\),\(M\) 是一个质数。
以及两个等式:
\(E = R * P mod M\)
\(R = E * Q mod M\)
现在知道了 \(P\) 和 \(Q\),且 \(P,Q,E < M\),求 \(R\)。
思路:
根据已知条件,容易知道 \(M\) 是 \(P * Q - 1\) 的一个质因数,让 \(P * Q - 1 = k * M\),那么 \(k * M\) 是 \(P * Q - 1\) 的最大质因数。
所以可以求出 \(M\),然后判断是不是满足题目中的条件就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
LL p, q, e;
cin >> p >> q >> e;
LL km = p * q - 1, m = -1;
for (LL i = 2; i <= km / i; i ++ ){
if (km % i == 0){
while(km % i == 0){
km /= i;
}
m = i;
}
}
if (km > 1) m = km;
if (m == -1 || p >= m || q >= m) cout << "shuanQ\n";
else cout << e * q % m << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
K - DOS Card
题意:
给定长为 \(n\) 的序列,选择一个对\(i, j(i < j\),可以得到 \((a_i + a_j) * (a_i - a_j)\) 的分数,\(m\) 次询问,每次询问一个区间 \(L, R\),问从这个区间中选择两对(四个下标要两两不相同),最多能得到多少分。
思路:
\((a_i + a_j) * (a_i - a_j)\)
= \(a_i^2 - a_j^2\)
因为选择了两对,所以答案由四个值组成,有两种情况。
++--, +-+-
将它们拆分开
++-, +--, +-+, -+-
再拆
++, +-, --, -+
再拆
+, -
所以通过线段树维护这十二个变量即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
const LL INF = 1e18;
LL w[N];
struct Segt{
struct node{
LL l, r;
LL ans1, ans2; //++-- +-+-
LL v1, v2, v3, v4; //++- +-- +-+ -+-
LL v5, v6, v7, v8; //++ +- -- -+
LL a, b; //+ -
}tr[N << 2];
void pushup(node &root, node left, node right){
root.ans1 = max({left.ans1, right.ans1, left.v1 + right.b, left.a + right.v2, left.v5 + right.v7});
root.ans2 = max({left.ans2, right.ans2, left.v3 + right.b, left.a + right.v4, left.v6 + right.v6});
root.v1 = max({left.v1, right.v1, left.v5 + right.b, left.a + right.v6});
root.v2 = max({left.v2, right.v2, left.v6 + right.b, left.a + right.v7});
root.v3 = max({left.v3, right.v3, left.v6 + right.a, left.a + right.v8});
root.v4 = max({left.v4, right.v4, left.v8 + right.b, left.b + right.v6});
root.v5 = max({left.v5, right.v5, left.a + right.a});
root.v6 = max({left.v6, right.v6, left.a + right.b});
root.v7 = max({left.v7, right.v7, left.b + right.b});
root.v8 = max({left.v8, right.v8, left.b + right.a});
root.a = max(left.a, right.a);
root.b = max(left.b, right.b);
}
void build(LL u, LL l, LL r){
tr[u] = {l, r,
-INF, -INF,
-INF, -INF, -INF, -INF,
-INF, -INF, -INF, -INF,
-INF, -INF};
if (l == r){
tr[u].a = w[l];
tr[u].b = -w[l];
return;
}
LL mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
node query(LL u, LL l, LL r){
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
LL mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
else if (l > mid) return query(u << 1 | 1, l, r);
else{
node t = {0, 0,
-INF, -INF,
-INF, -INF, -INF, -INF,
-INF, -INF, -INF, -INF,
-INF, -INF};
pushup(t, query(u << 1, l, r), query(u << 1 | 1, l, r));
return t;
}
}
}segt;
void solve(){
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> w[i];
w[i] *= w[i];
}
segt.build(1, 1, n);
while(m -- ){
int L, R;
cin >> L >> R;
auto t = segt.query(1, L, R);
cout << max(t.ans1, t.ans2) << "\n";
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
L - Luxury cruise ship
题意:
物品价格为 \(n\),现在有三种货币,分别为 7,31,365,问最少花几个货币可以刚好买物品。
思路:
先预处理一个范围中的价格最少花多少货币,然后范围外的用 365 去买。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e7 + 10;
LL dp[N];
void solve(){
LL n;
cin >> n;
LL k = 0;
if (n > N) k = (n - N) / 365 + 1;
n -= k * 365;
if (dp[n] > 1e18) cout << "-1\n";
else cout << k + dp[n] << "\n";
}
void init(){
memset(dp, 0x3f, sizeof dp);
dp[7] = 1, dp[31] = 1, dp[365] = 1;
for (int j = 7; j < N; j ++ ){
if (~dp[j]){
if (j + 7 < N) dp[j + 7] = min(dp[j] + 1, dp[j + 7]);
if (j + 31 < N) dp[j + 31] = min(dp[j] + 1, dp[j + 31]);
if (j + 365 < N) dp[j + 365] = min(dp[j] + 1, dp[j + 365]);
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
init();
LL T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}