DS:做题记录 // 230218
听说是 ds rash,有点开心,总算不用调搜索了。
A. 1-06E. nocriz 维护括号序列
http://222.180.160.110:1024/contest/3331/problem/1
在 gm 的强迫下开了这道题……
我不会 gm 说的栈做法,所以瞎胡了一个线段树做法。
不妨假设 (
是 1,)
是 -1,那么明显当整个序列的和为 0 时能配对。
然后对于 )(
的情况,我们判断一下前缀和的最小值即可(所以要维护的是前缀和的最小值)。
因为只有小括号嘛,所以会少很多稀奇古怪的情况(比如 [(])
之类,这样就有点难解决了)。
namespace XSC062 {
using namespace fastIO;
#define lt (p << 1)
#define rt (lt | 1)
const int maxn = 2e5 + 5;
struct _ {
int v, d;
int l, r;
};
int a[maxn];
int n, q, x;
char s[maxn];
_ t[maxn << 2];
inline int min(int x, int y) {
return x < y ? x : y;
}
inline void pushup(int p) {
t[p].v = min(t[lt].v, t[rt].v);
return;
}
inline void pushdown(int p) {
t[lt].v += t[p].d;
t[rt].v += t[p].d;
t[lt].d += t[p].d;
t[rt].d += t[p].d;
t[p].d = 0;
return;
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
t[p].v = a[l];
return;
}
int mid = (l + r) >> 1;
bld(lt, l, mid);
bld(rt, mid + 1, r);
pushup(p);
return;
}
inline void upd(int p, int l, int r, int v) {
if (l <= t[p].l && t[p].r <= r) {
t[p].v += v;
t[p].d += v;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
pushdown(p);
if (l <= mid)
upd(lt, l, r, v);
if (r > mid)
upd(rt, l, r, v);
pushup(p);
return;
}
inline int qry(int p, int x) {
if (t[p].l == t[p].r)
return t[p].v;
int mid = (t[p].l + t[p].r) >> 1;
pushdown(p);
if (x <= mid)
return qry(lt, x);
return qry(rt, x);
}
int main() {
read(n), read(q);
scanf("%s", s + 1);
for (int i = 1; i <= n; ++i) {
a[i] = s[i] == '(' ? 1 : -1;
a[i] += a[i - 1];
}
bld(1, 1, n);
while (q--) {
read(x);
if (s[x] == '(')
upd(1, x, n, -2), s[x] = ')';
else upd(1, x, n, 2), s[x] = '(';
if (qry(1, n) || t[1].v < 0)
putchar('0');
else putchar('1');
}
return 0;
}
} // namespace XSC062
B. 1-07D. 一姬的三倍满自动机
http://222.180.160.110:1024/contest/3331/problem/2
一道简单的字典树。好哎,反正字典树也是树,我喜欢树树题!
不难发现题目本身是非常之简单的,没什么含金量,就是考你还记不记得 Trie 怎么写的。
值得注意的是,Trie 数组的第一维所需大小应为字符串总长度,即 \(30\times n\)。
namespace XSC062 {
using namespace fastIO;
const int maxm = 3e7 + 5;
const int maxn = 1e6 + 5;
int T[maxm][2];
int a[maxn], p[maxn];
int n, x, tot = 1, res;
inline int max(int x, int y) {
return x > y ? x : y;
}
inline void Insert(void) {
int f = 1;
for (int i = 30; ~i; --i) {
if (!T[f][p[i]])
T[f][p[i]] = ++tot;
f = T[f][p[i]];
}
return;
}
inline int qry(void) {
int res = 0, f = 1;
for (int i = 30; ~i; --i) {
if (T[f][!p[i]]) {
f = T[f][!p[i]];
res += (1 << i);
}
else f = T[f][p[i]];
}
return res;
}
int main() {
read(n), read(x);
for (int i = 1; i <= n; ++i) {
read(a[i]);
for (int j = 30; ~j; --j) {
if ((a[i] ^ x) & (1 << j))
p[j] = 1;
else p[j] = 0;
}
res = max(res, qry());
for (int j = 30; ~j; --j) {
if (a[i] & (1 << j))
p[j] = 1;
else p[j] = 0;
}
Insert();
}
print(res);
return 0;
}
} // namespace XSC062
C. 1-06F. nocriz 与队列计算机
http://222.180.160.110:1024/contest/3331/problem/3
听说是道线段树,但是我想不出来怎么做。
所以可以暴力一点,我们先把所有加法人乘上从他往后所有乘法人的值,然后计算和。算出来的和呢,拿去除以询问区间右边所有乘法人之积再模 998244353 意义下的逆元就可以辽。
那修改呢?我们分修改哪个人和改成什么人来看。
-
修改哪个人
如果要修改一个乘法人,那我们就把这个乘法人之前的所有加法人的值乘上这个乘法人的逆元(相当于撤销乘上他的这一步操作)。
如果要修改一个加法人,我们把它的权值改成 0。
-
改成什么人
如果要改成一个乘法人,那我们就把它之前的所有加法人的权值乘上这个乘法人的权值。
如果要改成一个加法人,我们把它的权值乘上它往后的所有乘法人的权值。
值得注意的是,和以前我们做过的加法乘法线段树不一样,这个线段树下传懒标记的时候是不会传到区间乘积上去的。举个例子,+1 (*2) (*3)
,我们在前面加上一个 *7
,最后的结果是 (*7) +7 (*2) (*3)
。
所以我们的线段树要维护的内容就是区间和以及区间积。然后就好辽。又是一个费马,gm 你没有心!
恶心人的线段树…… 写得我写到后来都想放弃这个南墙法……
一定要注意到 每个 加法和乘法都要取模呀!!不然会挂到飞起(指 10pts)
建议直接文件内查找 +
和 *
!!
(拍桌而起)我居然是 The Longest!第二名码长比我少 2K!离谱!
namespace XSC062 {
using namespace fastIO;
#define lt (p << 1)
#define rt (lt | 1)
const int maxn = 5e5 + 5;
const int mod = 998244353;
struct _ {
int l, r;
int p, m, d;
};
_ t[maxn << 2];
int n, q, op, l, r, x;
int a[maxn], ty[maxn], ne[maxn];
inline int qkp(int x, int y) {
int res = 1;
while (y) {
if (y & 1)
(res *= x) %= mod;
(x *= x) %= mod;
y >>= 1;
}
return res;
}
inline int inv(int x) {
return qkp(x, mod - 2);
}
inline void pushup(int p) {
t[p].p = (t[lt].p + t[rt].p) % mod;
t[p].m = (t[lt].m * t[rt].m) % mod;
return;
}
inline void pushdown(int p) {
(t[lt].p *= t[p].d) %= mod;
(t[rt].p *= t[p].d) %= mod;
(t[lt].d *= t[p].d) %= mod;
(t[rt].d *= t[p].d) %= mod;
t[p].d = 1;
return;
}
void bld(int p, int l, int r) {
t[p].d = 1;
t[p].l = l, t[p].r = r;
if (l == r) {
if (ty[l] == 1) {
t[p].p = (ne[l] * a[l]) % mod;
t[p].m = 1;
}
else t[p].m = a[l];
return;
}
int mid = (l + r) >> 1;
bld(lt, l, mid);
bld(rt, mid + 1, r);
pushup(p);
return;
}
void delp(int p, int x) {
if (t[p].l == t[p].r) {
t[p].p = 0;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid)
delp(lt, x);
else delp(rt, x);
pushup(p);
return;
}
void delm(int p, int x) {
if (t[p].l == t[p].r) {
t[p].m = 1;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
delm(lt, x);
else delm(rt, x);
pushup(p);
return;
}
void addp(int p, int x, int v) {
if (t[p].l == t[p].r) {
t[p].p = v;
t[p].m = t[p].d = 1;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid)
addp(lt, x, v);
else addp(rt, x, v);
pushup(p);
return;
}
void addm(int p, int l, int r, int v) {
if (l <= t[p].l && t[p].r <= r) {
(t[p].d *= v) %= mod;
(t[p].p *= v) %= mod;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
addm(lt, l, r, v);
if (r > mid)
addm(rt, l, r, v);
pushup(p);
return;
}
void addm(int p, int x, int v) {
if (t[p].l == t[p].r) {
t[p].m = v;
t[p].p = 0, t[p].d = 1;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
addm(lt, x, v);
else addm(rt, x, v);
pushup(p);
return;
}
int qrym(int p, int l, int r) {
if (l <= t[p].l && t[p].r <= r)
return t[p].m;
pushdown(p);
int res = 1;
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
res = qrym(lt, l, r);
if (r > mid)
(res *= qrym(rt, l, r)) %= mod;
return res;
}
int qryp(int p, int l, int r) {
if (l <= t[p].l && t[p].r <= r)
return t[p].p;
pushdown(p);
int res = 0;
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid)
res = qryp(lt, l, r);
if (r > mid)
(res += qryp(rt, l, r)) %= mod;
return res;
}
int main() {
read(n), read(q);
for (int i = 1; i <= n; ++i)
read(ty[i]), read(a[i]);
ne[n + 1] = 1;
for (int i = n; i; --i) {
ne[i] = ne[i + 1];
if (ty[i] == 0)
(ne[i] *= a[i]) %= mod;
}
bld(1, 1, n);
while (q--) {
read(op), read(l);
read(r), read(x);
if (op == 0) {
if (ty[l] == 0) {
addm(1, 1, l, inv(a[l]));
delm(1, l);
}
else delp(1, l);
ty[l] = r, a[l] = x;
if (ty[l] == 0) {
addm(1, 1, l, a[l]);
addm(1, l, a[l]);
}
else {
addp(1, l, (a[l] *
qrym(1, l + 1, n)) % mod);
}
}
else {
print(((x * qrym(1, l, r)) % mod +
(qryp(1, l, r) *
inv(qrym(1, r + 1, n)))
% mod) % mod, '\n');
}
}
return 0;
}
} // namespace XSC062
D. nocriz 送温暖
http://222.180.160.110:1024/contest/3331/problem/4
一道简单的逆元题啊,注意到模数是质数,甚至可以费马小定理。
不对,所以这和 DS 有什么关系?哦,好像正解是线段树还是树状数组来着,但感觉就码量来说不如费马!!!
namespace XSC062 {
using namespace fastIO;
const int maxn = 2e5 + 5;
const int mod = 998244353;
int a[maxn];
int n, q, l, r;
inline int qkp(int x, int y) {
int res = 1;
while (y) {
if (y & 1)
(res *= x) %= mod;
(x *= x) %= mod;
y >>= 1;
}
return res;
}
inline int inv(int x) {
return qkp(x, mod - 2);
}
int main() {
read(n), read(q);
a[0] = 1;
for (int i = 1; i <= n; ++i) {
read(a[i]);
(a[i] *= a[i - 1]) %= mod;
}
while (q--) {
read(l), read(r);
print((a[r] * inv(a[l - 1])) % mod, '\n');
}
return 0;
}
} // namespace XSC062
E. 1-06G. nocriz 和巨佬谈人生
http://222.180.160.110:1024/contest/3331/problem/5
woc,删题力。
那就 http://222.180.160.110:1024/problem/4691
盒盒盒我居然看了半天才看出来 50 15 4 的意思是金牌,国集和 IOI
那 1 是啥?当然是 AK IOI!膜拜 AprilGrimoire 佬,虽然我并不认识您!
开完 T4 就直奔这道题辽。因为我喜欢树树题(才不是因为自动跳转)!
题意好复杂啊,我把我做非连的寄巧都用上了才看懂。
就是说呢,有一些操作会给你在树根生成 \(k\) 个人,你需要让这 \(k\) 个人 依次 完成如下操作:
寻找编号最小的一个节点,满足它和根之间有节点上没站人,然后让这个人站到这个编号最小的节点和根的路径上最深的一个没人的节点上去。
然后清空操作呢,就是说目标节点到根节点的路径上所有人往下走一步,其实就是说把路径上深度最小的一个点删掉(所以目标节点本身是不用管滴)。
看了一眼输入,感觉像是树剖,但 gm 说这是树形 DP???爷青结!!!
不过为了发扬我们传统的不撞南墙不回头的精神,我们先考虑树剖。
不难发现操作 2 很简单,只用看一眼根节点到目标节点上有几个节点站了人就可以辽。
对于操作 1,我们不妨乱猜一个结论:树上的总人数已知时,他们的站位是固定的。
考虑到操作 2,我们只是删除了目标点到根节点路径上深度最小的节点,所以要求这个节点必须是最后一个加入这个树的……
那这也太扯淡了,样例的图都能把你 Hack 了,所以我们需要考虑别的方案。从刚刚那个假结论出发,不难想到,所有节点都有一个互不相同的选择优先级,新加入的人会根据剩余节点的优先级站过去。然而这个思路转化目前看起来并没有什么用,因为我们还是要一个一个去更新加入的人,一旦来了个菊花图就可以起飞。总之就是,我们绕不开整体更新。
呃呃呃,为了防止找不到了我先把 nocriz 的代码从 XJTUOJ 上毛了下来,等做不出来了再看吧……
https://oj.xjtuicpc.com/submission/7632
#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <iomanip>
#include <bitset>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define debug(x) cerr << #x << '=' << x << endl
#define set0(x) memset(x, 0, sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<ll, double> pid;
template <typename T>
void read(T &x) {
x = 0;
char ch = getchar();
ll f = 1;
while (!isdigit(ch)) {
if (ch == '-')
f *= -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
x *= f;
}
const int N = 100010;
int n, q, fa[N][20];
vector<int> G[N];
int mi[N], rk[N];
vector<int> P;
bool cmp(int a, int b) { return mi[a] < mi[b]; }
void dfs1(int num) {
mi[num] = num;
for (auto ct : G[num]) {
dfs1(ct);
mi[num] = min(mi[num], mi[ct]);
}
}
void dfs2(int num) {
sort(G[num].begin(), G[num].end(), cmp);
for (auto ct : G[num]) {
dfs2(ct);
}
rk[num] = P.size();
P.push_back(num);
}
int vis[N];
int main() {
read(n);
read(q);
int rt = -1;
for (int i = 1; i <= n; i++) {
read(fa[i][0]);
if (fa[i][0] == 0) {
rt = i;
} else {
G[fa[i][0]].push_back(i);
}
}
for (int j = 1; j < 20; j++) {
for (int i = 1; i <= n; i++) fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
dfs1(rt);
dfs2(rt);
set<int> ety;
set<int>::iterator it;
for (int i = 0; i < n; i++) ety.insert(i);
for (int t = 0; t < q; t++) {
int op, ov;
read(op);
read(ov);
if (op == 1) {
it = ety.begin();
queue<int> Q;
for (int i = 0; i < ov; i++) {
Q.push(*it);
it++;
}
it--;
cout << P[*it] << endl;
while (!Q.empty()) {
int c = Q.front();
Q.pop();
ety.erase(c);
vis[P[c]] = 1;
}
}
if (op == 2) {
int cc = 0;
for (int i = 19; i >= 0; i--) {
if (fa[ov][i] != 0 && vis[fa[ov][i]]) {
cc += 1 << i;
ov = fa[ov][i];
}
}
vis[ov] = 0;
ety.insert(rk[ov]);
cout << cc << endl;
}
}
return 0;
}
—— · EOF · ——
真的什么也不剩啦 😖