2022“杭电杯”中国大学生算法设计超级联赛(8) 题解
作者:互联网
A. Theramore
考虑只对长度为3的子串进行操作,发现偶数位置的字符不会出现在奇数位置,奇数位置的字符不会出现在偶数位置。
对奇偶位置字符进行排序即可。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
string S;
char T[MAXN];
int num[3], n;
void Solve() {
cin >> S; n = S.length();
num[0] = num[1] = 0;
for(int i = 0; i < n; i += 2) num[ S[i] - '0' ]++;
for(int i = 0; i < n; i += 2) {
if(num[0]) num[0]--, T[i] = '0';
else num[1]--, T[i] = '1';
}
num[0] = num[1] = 0;
for(int i = 1; i < n; i += 2) num[ S[i] - '0' ]++;
for(int i = 1; i < n; i += 2) {
if(num[0]) num[0]--, T[i] = '0';
else num[1]--, T[i] = '1';
}
for(int i = 0; i < n; i++) cout << T[i]; cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
B. Darkmoon Faire
设 \(f[i]\) 为 \([1,i]\) 的划分方案数,则 \(f[i]\) 可以从 \(f[j],j<i\) 转移,问题转化成快速找符合条件的 \(j\)。
首先利用单调栈维护以 \(i\) 为结尾的区间最大值和区间最小值。
以最大值为例(最小值同理),设单调栈里相邻的两个位置为 \(x, y\),此时如果选择 \(s \in (x, y]\),则 \([s, i]\) 的区间最大值位置一定在 \(y\)。
如果 \(y\) 为奇数/偶数,则 \(s\) 必须为奇数/偶数,这样才能满足最大值位置在奇数的情况。
对所有奇数位置和所有偶数位置分别建一个线段树,线段树维护两个变量:区间中所有点覆盖次数的最大值 \(st\),区间中满足覆盖次数等于 \(st\) 的 \(f[i]\) 之和 \(sum\)。
将 \((x, y]\) 中所有的奇数/偶数位置提出来进行区间覆盖。对于 \(f[i]\),满足条件的 \(j\) 一定会被覆盖两次。可利用线段树进行区间查询。
查询完后需要将 \(f[i]\) 放入线段树并更新 \(sum\)。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 3e5 + 5;
const int MOD = 998244353;
struct Node {
int sum, st, tag;
}Odd[MAXN << 2], Even[MAXN << 2];
int n, f[MAXN], id[MAXN], a[MAXN];
vector<int> Max, Min;
void PushUp(Node* tree, int l, int r, int p) {
if(tree[p << 1].st == tree[p << 1 | 1].st) tree[p].sum = (tree[p << 1].sum + tree[p << 1 | 1].sum) % MOD;
if(tree[p << 1].st > tree[p << 1 | 1].st) tree[p].sum = tree[p << 1].sum;
if(tree[p << 1].st < tree[p << 1 | 1].st) tree[p].sum = tree[p << 1 | 1].sum;
tree[p].st = max(tree[p << 1].st, tree[p << 1 | 1].st);
}
void PushDown(Node* tree, int l, int r, int p) {
tree[p << 1].tag += tree[p].tag;
tree[p << 1 | 1].tag += tree[p].tag;
tree[p << 1].st += tree[p].tag;
tree[p << 1 | 1].st += tree[p].tag;
tree[p].tag = 0;
}
void Build(int l = 1, int r = n, int p = 1) {
Odd[p].sum = Odd[p].st = Odd[p].tag = 0;
Even[p].sum = Even[p].st = Even[p].tag = 0;
if(l == r) return ;
int mid = l + r >> 1;
Build(l, mid, p << 1);
Build(mid + 1, r, p << 1 | 1);
}
void ModifySt(Node* tree, int nl, int nr, int val, int l = 1, int r = n, int p = 1) {
if(nl > nr || nr < l || nl > r) return ;
if(nl <= l && nr >= r) {
tree[p].tag += val;
tree[p].st += val;
return ;
}
PushDown(tree, l, r, p);
int mid = l + r >> 1;
if(nl <= mid) ModifySt(tree, nl, nr, val, l, mid, p << 1);
if(nr > mid) ModifySt(tree, nl, nr, val, mid + 1, r, p << 1 | 1);
PushUp(tree, l, r, p);
}
void ModifySum(Node* tree, int loc, int val, int l = 1, int r = n, int p = 1) {
if(loc < l || loc > r) return ;
if(l == r) {
tree[p].sum = val;
return ;
}
PushDown(tree, l, r, p);
int mid = l + r >> 1;
if(loc <= mid) ModifySum(tree, loc, val, l, mid, p << 1);
else ModifySum(tree, loc, val, mid + 1, r, p << 1 | 1);
PushUp(tree, l, r, p);
}
void CalcMax(int l, int r, int val) {
//cout << "calcmax : " << l << " " << r << " " << val << "\n";
if(r % 2) {
if(l % 2 == 0) ++l;
ModifySt(Odd, id[l], id[r], val);
} else {
if(l % 2 == 1) ++l;
ModifySt(Even, id[l], id[r], val);
}
}
void CalcMin(int l, int r, int val) {
//cout << "calcmin : " << l << " " << r << " " << val << "\n";
if(r % 2) {
if(l % 2 == 1) ++l;
ModifySt(Even, id[l], id[r - 1], val);
} else {
if(l % 2 == 0) ++l;
ModifySt(Odd, id[l], id[r - 1], val);
}
}
void Solve() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
int cnt = 0;
for(int i = 1; i <= n; i += 2) id[i] = ++cnt;
cnt = 0;
for(int i = 0; i <= n; i += 2) id[i] = ++cnt;
Build();
while( !Max.empty() ) Max.pop_back();
Max.push_back(0);
while( !Min.empty() ) Min.pop_back();
Min.push_back(0);
f[0] = 1; ModifySum(Odd, id[1], f[0]);
for(int i = 1; i <= n; i++) {
//cout << i << " i:\n";
while( Max.back() && a[ Max.back() ] < a[i] ) {
int b = Max.back();
Max.pop_back();
CalcMax(Max.back() + 1, b, -1);
}
CalcMax(Max.back() + 1, i, 1);
Max.push_back(i);
while( Min.back() && a[ Min.back() ] > a[i] ) {
int b = Min.back();
Min.pop_back();
CalcMin(Min.back() + 1, b, -1);
}
CalcMin(Min.back() + 1, i, 1);
Min.push_back(i);
f[i] = 0;
if(Odd[1].st == 2) f[i] += Odd[1].sum;
if(Even[1].st == 2) f[i] += Even[1].sum;
f[i] %= MOD;
if(i == n) break;
if(i & 1) ModifySum(Even, id[i + 1], f[i]);
else ModifySum(Odd, id[i + 1], f[i]);
}
//for(int i = 0; i <= n; i++) cout << f[i] << " "; cout << "f\n";
cout << f[n] << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
E. Ironforge
先不考虑向左走的情况,预处理每个点向右走最远能到达哪,设 \(i\) 向右走最远能到 \(R[i]\),向左走最远能到 \(L[i]\)
具体思路是从大到小枚举点,对于枚举到的 \(i\),先判断 \(i\) 能不能走到 \(i+1\),能的话就代表其也能走到 \(R[i+1]\)。继续判断能不能走到 \(R[i+1]+1\),直至走到边界或者不能继续走时退出。
考虑从小到大枚举每个点,向左扩展区间,分为以下几种情况:
① \(i\) 和 \(i-1\) 互相都能走到,此时更新 \(R[i]=max(R[i],R[i-1]),\ L[i]=L[i-1]\)
② \(i\) 无论如何都走不到 \(i-1\),此时更新 \(L[i]=i\)
③ \(i\) 能走到 \(i-1\),但 \(i-1\) 走不到 \(i\),此时反复扩展左右区间直至不能继续走下去或走到边界
感性理解一下,对于上述操作,我们每条边如果查询成功,则被合并到当前点,下次查询时直接跳过这条边。如果查询失败则退出,最多有 \(O(n)\) 次查询失败。
每次查询可以用vector存某个质数出现的所有点,二分查找位置即可。
时间复杂度 \(O(n \log n)\)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
int n, m, a[MAXN], b[MAXN];
int cnt, isprime[MAXN], prime[MAXN], Map[MAXN];
int L[MAXN], R[MAXN];
vector<int> pos[MAXN];
bool Query(int l, int r, int p) {
int posl = lower_bound(pos[p].begin(), pos[p].end(), l) - pos[p].begin(), posr = upper_bound(pos[p].begin(), pos[p].end(), r) - pos[p].begin() - 1;
return (posr - posl + 1 > 0);
}
void Solve() {
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i < n; ++i) cin >> b[i];
for(int i = 1; i <= n; ++i) pos[i].clear();
for(int i = 1; i <= n; ++i) {
int cur = a[i];
for(int j = 1; j <= prime[0] && prime[j] * prime[j] <= cur; ++j) {
if(cur % prime[j]) continue;
while(cur % prime[j] == 0) cur /= prime[j];
pos[ Map[ prime[j] ] ].push_back(i);
}
if(Map[cur]) pos[ Map[cur] ].push_back(i);
}
for(int i = n; i >= 1; --i) {
R[i] = i;
while(R[i] < n && Query(i, R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ];
}
for(int i = 1; i <= n; ++i) {
if(i > 1 && R[i - 1] >= i) {
if( Query(i, R[i], Map[ b[i - 1] ]) ) L[i] = L[i - 1], R[i] = max(R[i], R[i - 1]);
else L[i] = i;
} else {
L[i] = i;
while(1) {
bool flag = 1;
while(L[i] > 1 && Query(L[i], R[i], Map[ b[ L[i] - 1 ] ]) ) L[i] = L[ L[i] - 1 ], flag = 0;
while(R[i] < n && Query(L[i], R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ], flag = 0;
if(flag) break;
}
}
}
for(int k = 1; k <= m; ++k) {
int x, y; cin >> x >> y;
if(L[x] <= y && y <= R[x]) cout << ("Yes\n");
else cout << ("No\n");
}
}
void Init(int n) {
cnt = 0;
isprime[1] = 1;
for(int i = 2; i <= n; i++) {
if(!isprime[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
isprime[ i * prime[j] ] = 1;
if(i % prime[j] == 0) break;
}
}
prime[0] = cnt;
for(int i = 1; i <= cnt; i++) Map[ prime[i] ] = i;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
Init(2e5);
while(T--) Solve();
return 0;
}
H. Orgrimmar
设 \(f[u][0/1/2]\) 分别为 ① \(u\) 不被选中;② \(u\) 被选中且不与儿子 \(v\) 连边;③ \(u\) 被选中且与儿子 \(v\) 连边 的最大集合数。
有
\[f[u][0] = \sum\max(f[v][0], f[v][1], f[v][2]) \\ f[u][1] = \sum f[v][0] + 1 \\ f[u][2] = \sum f[v][0] - \min(f[v][0] - f[v][1]) + 1 \\ \]直接跑个树形dp即可。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
int f[MAXN][4], n;
vector<int> G[MAXN];
void DFS(int u, int fa) {
int sum = 0;
f[u][0] = f[u][1] = f[u][2] = 0;
for(auto v : G[u]) {
if(v == fa) continue;
DFS(v, u);
f[u][0] += max({f[v][0], f[v][1], f[v][2]});
sum += f[v][0];
f[u][1] += f[v][0];
}
f[u][1]++;
for(auto v : G[u]) {
if(v == fa) continue;
f[u][2] = max(f[u][2], sum - f[v][0] + f[v][1]);
}
f[u][2]++;
}
void Solve() {
cin >> n;
for(int i = 1; i <= n; i++) G[i].clear();
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
DFS(1, 0);
//for(int i = 1; i <= n; i++) cout << f[i][0] << " "; cout << "f0\n";
//for(int i = 1; i <= n; i++) cout << f[i][1] << " "; cout << "f1\n";
//for(int i = 1; i <= n; i++) cout << f[i][2] << " "; cout << "f2\n";
cout << max({f[1][0], f[1][1], f[1][2]}) << "\n";
}
signed main()
{
int size(512<<20); // 512M
__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
exit(0);
}
I. Gilneas
考虑计算一条边 \(u-v\) 对答案的贡献,其中 \(u\) 是 \(v\) 的父亲。
要令这条边染色,有 \(v\) 及其子树中的点进行access操作成功,且其后的 \(u\) 及其子树中的点进行access操作失败。
用一个线段树对所有access操作维护两个变量:\(sum\) 和 \(prod\),其中 \(prod\) 为区间内 \((1-p_i)\) 之积,\(sum\) 为 \(u\) 及其子树中的点中的所有access操作的 \(c_i \cdot p_i\) 乘以紧随在其后的 \((1-p_i)\) 的和。
枚举每个点,首先将其儿子的线段树合并在该节点的线段树上,然后直接将所有区间的 \(sum\) 加到答案中。发现对 \(u\) 的access操作会影响到最终答案,需要减去这一部分。
我们直接暴力枚举对于 \(u\) 的access操作,计算该操作成功,其后的 \(u\) 及其子树中的点进行access操作失败的期望。
时间复杂度 \(O(nlogn)\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;
int qpow(int a, int p) {
int res = 1;
while(p) {
if(p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
int Add(int a, int b) { return (a + b + MOD * 2) % MOD; }
int Sub(int a, int b) { return (a - b + MOD * 2) % MOD; }
int Mul(int a, int b) { return (a * b % MOD + MOD) % MOD; }
int Div(int a, int b) { return (a * qpow(b, MOD - 2) % MOD + MOD) % MOD; }
struct Node {
int ls, rs, prod = 1, sum;
}tree[MAXN * 40], NUL;
int n, m, tot, root[MAXN], fa[MAXN];
vector<int> vec[MAXN];
void PushUp(int p) {
int ls = tree[p].ls, rs = tree[p].rs;
if(ls == 0 && rs == 0) {
tree[p] = NUL;
} else if(ls == 0) {
tree[p].prod = tree[rs].prod;
tree[p].sum = tree[rs].sum;
} else if(rs == 0) {
tree[p].prod = tree[ls].prod;
tree[p].sum = tree[ls].sum;
} else {
tree[p].prod = Mul(tree[ls].prod, tree[rs].prod);
tree[p].sum = Add(Mul(tree[ls].sum, tree[rs].prod), tree[rs].sum);
}
}
int Merge(int a, int b) {
if(a == 0 || b == 0) return a + b;
tree[a].ls = Merge(tree[a].ls, tree[b].ls);
tree[a].rs = Merge(tree[a].rs, tree[b].rs);
PushUp(a);
return a;
}
void Modify(int& p, int loc, int prod, int sum, int l = 1, int r = m) {
if(!p) tree[p = ++tot] = NUL;
if(l == r) { tree[p].prod = prod, tree[p].sum = sum; return ; }
int mid = l + r >> 1;
if(loc <= mid) Modify(tree[p].ls, loc, prod, sum, l, mid);
else Modify(tree[p].rs, loc, prod, sum, mid + 1, r);
PushUp(p);
}
int Query(int p, int loc, int rprod = 1, int l = 1, int r = m) {
if(l == r) return Mul(tree[p].sum, rprod);
int mid = l + r >> 1;
if(loc <= mid) return Query(tree[p].ls, loc, Mul(rprod, tree[ tree[p].rs ].prod), l, mid);
else return Query(tree[p].rs, loc, rprod, mid + 1, r);
}
void Solve() {
cin >> n >> m; tree[tot = 0] = NUL;
for(int i = 1; i <= n; i++) vec[i].clear(), root[i] = 0;
for(int i = 2; i <= n; i++) cin >> fa[i];
for(int i = 1; i <= m; i++) {
int x, c, p; cin >> x >> c >> p;
Modify(root[x], i, Sub(1, p), Mul(c, p) );
vec[x].push_back(i);
}
int ans = 0;
for(int i = n; i; i--) {
ans = Add(ans, tree[ root[i] ].sum );
for(auto j : vec[i]) ans = Sub(ans, Query(root[i], j) );
root[ fa[i] ] = Merge(root[ fa[i] ], root[i]);
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
标签:return,int,题解,sum,tree,MAXN,2022,杭电杯,MOD 来源: https://www.cnblogs.com/Orzjh/p/16584453.html