20250623 好题分享 x3
P7670 [JOI 2018 Final] 毒蛇越狱 / Snake Escaping
JOI 实验室有 2L 条毒蛇。蛇的编号为 0,1,⋯,2L−1。每条蛇从头到尾分为 L 个部分。每个部分的颜色是蓝色或红色。 对于毒蛇 i,令 i=∑k=1Lck2L−k(0≤ck≤1)为 i 的二进制表达式。那么,
如果 ck=0,毒蛇 i 从头开始的第 k 部分的颜色是蓝色,
如果 ck=1,毒蛇 i 从头开始的第 k 部分的颜色是红色。
每条毒蛇都有一个 0 到 9 之间的整数,包括 0 和 9,为毒性。给出一个由 0,1,2,3,4,5,6,7,8,9 组成的长度为 2L 的字符串 S。第 i 个字符(1≤i≤2L)是毒蛇 i−1 的毒性。由于毒蛇行动迅速,所以经常从 JOI 实验室逃走。住在实验室附近的人向 JOI 实验室投诉,他们看到毒蛇从实验室逃逸。您将收到 Q 天的投诉清单。 第 d 天的投诉(1≤d≤Q)是一个长度为 L 的字符串 Td,由 0,1,? 组成。
如果 Td 的第 j 个字符(1≤j≤L)为 0,这意味着第 d 天从实验室逃出的每条毒蛇的第 j 个部分是蓝色的,
如果 Td 的第 j 个字符(1≤j≤L)为 1,这意味着第 d 天从实验室逃出的每条毒蛇的第 j 部分是红色的,并且
如果 Td 的第 j 个字符(1≤j≤L)为 ?,这意味着人们没有提供关于第 d 天从实验室逃逸的毒蛇的第 j 部分的信息。
所有的投诉都是准确的信息。所有从实验室逃逸的毒蛇都在同一天被 JOI 实验室的工作人员收留。可能发生同一条蛇在不同的日子逃脱。
JOI 实验室执行主任 K 教授为了估计毒蛇逃逸的风险,想知道可能逃出实验室的毒蛇的毒性总和。 你的任务是编写一个程序,根据 Q 天的投诉列表,计算每天可能从实验室逃逸的蛇的毒性总和。
现给定描述毒蛇毒性的字符串 S 和 Q 天的投诉列表,请编写一个程序来计算每天可能从实验室逃逸的蛇的毒性总和。
输入 #1
3 5
12345678
000
0??
1?0
?11
???
输出 #1
1
10
12
12
36
有一个暴力,直接枚举?是0 / 1的情况,复杂度单次2cnt(?)的,还有一个茸赤,可以预处理所有第i位为0的所有数字的贡献,然后直接“筛选”,但是会算重复,所以需要茸赤,复杂度是2cnt(0)的,然后再弄出来复杂度是2^cnt(1)的情况,全都拼一块,复杂度就变成了1 * 2 ^ (n/3)的,可以通过。
P4211 [LNOI2014] LCA
给出一个 n 个节点的有根树(编号为 0 到 n−1,根节点为 0)。
一个点的深度定义为这个节点到根的距离 +1。
设 dep[i] 表示点 i 的深度,LCA(i,j) 表示 i 与 j 的最近公共祖先。
有 m 次询问,每次询问给出 l,r,z,求 ∑i=lrdep[LCA(i,z)] 。
输入格式
一个平凡的做法:
对一个点z,将所有区间内的点分为3类,在根节点z子树之外的,在z到根路径上及其路径上的点的子树的,在z子树的,直接扫描线。每次查询ans(1,r) - ans(1,l-1)。
但是有一个很巧妙的思路
在你加点时
直接把添加的一个点到根节点路径都加1,这样查到的正好就是LCA了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 50000 + 10;
constexpr int MOD = 201314;
int n, m;
struct Edge {
int to, next;
} e[N << 1];
int head[N], ecnt;
void add_edge(int u, int v) {
e[ecnt].to = v;
e[ecnt].next = head[u];
head[u] = ecnt++;
}
int fa[N], dep[N], sz[N], son[N], top[N];
int dfn[N], id[N], dfc;
void dfs1(int u, int f) {
fa[u] = f;
dep[u] = dep[f] + 1;
sz[u] = 1;
son[u] = 0;
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (v == f) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp;
dfn[u] = ++dfc;
id[dfc] = u;
if (son[u]) {
dfs2(son[u], tp);
}
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
inline int ls(int x){ return x<<1; }
inline int rs(int x){ return x<<1|1; }
int seg[N<<2], lz[N<<2];
void push_up(int rt) {
seg[rt] = (seg[ls(rt)] + seg[rs(rt)]) % MOD;
}
void push_down(int l, int r, int rt) {
if (!lz[rt]) return;
int mid = (l + r) >> 1;
int v = lz[rt];
seg[ls(rt)] = (seg[ls(rt)] + 1LL * v * (mid - l + 1)) % MOD;
seg[rs(rt)] = (seg[rs(rt)] + 1LL * v * (r - mid)) % MOD;
lz[ls(rt)] = (lz[ls(rt)] + v) % MOD;
lz[rs(rt)] = (lz[rs(rt)] + v) % MOD;
lz[rt] = 0;
}
void build(int l, int r, int rt) {
lz[rt] = 0;
if (l == r) {
seg[rt] = 0;
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls(rt));
build(mid+1, r, rs(rt));
push_up(rt);
}
void update(int l, int r, int rt, int L, int R, int val) {
if (L <= l && r <= R) {
seg[rt] = (seg[rt] + 1LL * val * (r - l + 1)) % MOD;
lz[rt] = (lz[rt] + val) % MOD;
return;
}
push_down(l, r, rt);
int mid = (l + r) >> 1;
if (L <= mid) update(l, mid, ls(rt), L, R, val);
if (R > mid) update(mid+1, r, rs(rt), L, R, val);
push_up(rt);
}
int query(int l, int r, int rt, int L, int R) {
if (L <= l && r <= R) {
return seg[rt];
}
push_down(l, r, rt);
int mid = (l + r) >> 1;
ll res = 0;
if (L <= mid) res += query(l, mid, ls(rt), L, R);
if (R > mid) res += query(mid+1, r, rs(rt), L, R);
return int(res % MOD);
}
void update_path(int u, int v, int val) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, n, 1, dfn[top[u]], dfn[u], val);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
update(1, n, 1, dfn[v], dfn[u], val);
}
int query_path(int u, int v) {
ll res = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res += query(1, n, 1, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
res += query(1, n, 1, dfn[v], dfn[u]);
return int(res % MOD);
}
struct Event {
int pos;
int z;
int idx;
int sign;
bool operator<(const Event &other) const {
return pos < other.pos;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
memset(head, -1, sizeof(head));
ecnt = 0;
for (int i = 2; i <= n; i++) {
int f;
cin >> f;
f++;
add_edge(f, i);
add_edge(i, f);
}
dep[0] = 0;
dfs1(1, 0);
dfc = 0;
dfs2(1, 1);
build(1, n, 1);
vector<Event> events;
events.reserve(2*m);
for (int i = 1; i <= m; i++) {
int l, r, z;
cin >> l >> r >> z;
l++; r++; z++;
events.push_back({r, z, i, +1});
if (l > 1) {
events.push_back({l-1, z, i, -1});
}
}
sort(events.begin(), events.end());
vector<ll> ans(m+1, 0);
int now = 1;
for (auto &ev : events) {
int pos = ev.pos;
while (now <= pos) {
update_path(now, 1, 1);
now++;
}
int sum = query_path(ev.z, 1);
ans[ev.idx] = (ans[ev.idx] + 1LL * ev.sign * sum) % MOD;
if (ans[ev.idx] < 0) ans[ev.idx] += MOD;
}
for (int i = 1; i <= m; i++) {
cout << ans[i] << "\n";
}
return 0;
}
P3564 [POI 2014] BAR-Salad Bar
有一个长度为n的字符串,每一位只会是p或j。求一个最长子串,使得不管是从左往右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数。
有一个很复杂的扫描线
也有一个很复杂的单调占+二分
但是
将字母转为-1/1
记前缀和为sum。 而对于一个合法的区间[l,r],一定会有sum[l]<=sum[i]<=sumr
所以求出每一个点i对应的最远的满足条件的长度就行了。
浙公网安备 33010602011771号