D. 圆
不会这个题。这个做法是有人告诉我的。
经典把序列复制一遍放在后面,那么每一个长度为 的区间都是一种可能的环。
跑一次类似 CF1399F 的做法。每个长度为 的区间算一下。
E. 外向树
生まれた時から知ってたのさ
この街に雪が降ることを
それこそ宇宙が始まるずっと前から
君と僕が出会うように
小清新 DS。
限制转化成点集 的虚树上,叶节点的个数。
一个点 会被计入贡献,当且仅当其子树里面没有任何一个点 也处在点集中,那也就是说:
任意性命题总是不太好处理的,如果只用判断 个位置就好了。
设 的子树中所有点编号的集合中,点 在编号上的前驱后继,分别记作 和 。那么点 满足条件意味着 。
因为 ,所以大于和小于都只用判一侧,从而:
另一侧对称。
通过启发式合并或线段树合并等手段,求出 和 是容易的。
一次询问的答案就是求有多少个 同时满足
也就是有 个四元组 ,每次询问给定一个四元组 ,求这 个四元组中有多少个与询问的四元组满足偏序关系 。这是个四维偏序,可以强行 解决。
区间的限制带来了两个维度,但是转化到这一步之后答案就满足了可差分性,经典差分减一维,把询问差分成 的前缀以及 的前缀,答案就是后者减前者。分治或者树套树解决即可。复杂度 。
似乎有 爆标做法。
/*
* Copyright© 2024 Ackerlanna & Heratino. All rights reserved.
* author: Ackerlanna & Heratino
* Problem: 【牛客练习赛122】 E
* Tag: 虚树, 启发式合并, 分治, 数据结构
* Memory Limit: 512MiB
* Time Limit: 1000ms
* Source: 牛客练习赛122
*/
// Narcissu / どうか安寧な記憶を
#include <bits/stdc++.h>
using i64 = long long;
//{{{闪耀的一等星
char buf[1 << 20], *P1 = buf, *P2 = buf;
inline char gc() {
if (P1 == P2)
P2 = (P1 = buf) + fread(buf, 1, 1 << 20, stdin);
return P1 == P2 ? EOF : *P1++;
}
inline i64 read() {
i64 s = 0, w = 1;
char c = gc();
while (!isdigit(c)) {
if (c == '-')
w = -1;
c = gc();
}
while (isdigit(c))
s = (s << 3) + (s << 1) + (c ^ 48), c = gc();
return s * w;
}
//}}}
constexpr int N = 1e5 + 10;
int a[N];
int n, m;
std::basic_string<int> G[N];
inline void add(int u, int v) {G[u] += v;}
int fa[N], dep[N], siz[N], son[N], dfc;
void dfs1(int u) {
dep[u] = dep[fa[u]] + 1;
siz[u] = 1;
for (const int &v : G[u]) {
if (v == fa[u])
continue;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
}
int prev[N], succ[N];
std::set<int> number;
inline void del0(int u) {
for (const int &v : G[u]) {
if (v == fa[u])
continue;
del0(v);
}
number.erase(u);
}
inline void get0(int u) {
number.insert(u);
for (const int &v : G[u]) {
if (v == fa[u])
continue;
get0(v);
}
}
void dfs(int u, bool light) {
// 先递归轻儿子以防重儿子影响贡献
for (const int &v : G[u]) {
if (v == fa[u] || v == son[u])
continue;
dfs(v, true);
del0(v);
}
if (son[u]) dfs(son[u], false);
number.insert(u);
for (const int &v : G[u]) {
if (v == fa[u] || v == son[u])
continue;
get0(v);
}
auto it = number.find(u);
if (it != number.begin())
prev[u] = *std::prev(it);
else
prev[u] = -1;
if (std::next(it) != number.end())
succ[u] = *std::next(it);
else
succ[u] = n + 1;
if (light)
del0(u);
}
struct queries {
int p;
int L, R;
int num, C;
bool operator < (const queries &t) {
return p < t.p;
}
};
int ans[N];
std::vector<queries> q;
int tr[N];
inline int lowbit(int x) {return x & -x;}
inline void addT(int pos, int k) {
for (int i = pos; i < N; i += lowbit(i))
tr[i] += k;
}
inline int query(int pos) {
int res = 0;
for (int i = pos; i > 0; i -= lowbit(i))
res += tr[i];
return res;
}
int p[N];
void Divide(int l, int r, std::vector<queries> &qry) {
if (l == r) {
std::sort(qry.begin(), qry.end(), [&] (auto x, auto y) {
return x.L < y.L;
});
for (const auto &[pos, L, R, num, C] : qry) {
if (prev[p[l]] < L && succ[p[l]] > R)
ans[num] += C;
}
return ;
}
int mid = l + r >> 1;
std::vector<queries> lft, rgt;
for (const auto &x : qry) {
if (x.p <= mid)
lft.push_back(x);
else
rgt.push_back(x);
}
// 左右按 l 排序
Divide(l, mid, lft);
Divide(mid + 1, r, rgt);
int lpos = l;
for (const auto &[pos, L, R, num, C] : rgt) {
while (lpos <= mid && prev[p[lpos]] < L)
addT(succ[p[lpos++]], 1);
ans[num] += ((lpos - l) - query(R)) * C;
}
for (int i = l; i < lpos; i++)
addT(succ[p[i]], -1);
// 归并
std::inplace_merge(p + l, p + mid + 1, p + r + 1, [&](int x, int y) {
return prev[x] < prev[y];
});
std::merge(lft.begin(), lft.end(), rgt.begin(), rgt.end(), qry.begin(), [&](auto x, auto y) {
return x.L < y.L;
});
}
/* 无法忘却的记忆与苍蓝之梦 */
int main() {
#ifdef AckerlannaHeratino
freopen("input.txt", "r", stdin);
#endif
n = read(), m = read();
for (int i = 1; i < n; i++) {
int u = read(), v = read();
add(u, v), add(v, u);
}
dfs1(1);
dfs(1, true);
// for (int i = 1; i <= n; i++)
// std::cerr << prev[i] << " \n"[i == n];
// for (int i = 1; i <= n; i++)
// std::cerr << succ[i] << " \n"[i == n];
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
int l = read(), r = read();
if (l == r) {
ans[i] = 0;
} else {
if (l != 1)
q.push_back({l - 1, l, r, i, -1});
q.push_back({r, l, r, i, 1});
}
}
std::sort(q.begin(), q.end());
Divide(1, n, q);
for (int i = 1; i <= m; i++)
std::cout << ans[i] << '\n';
return 0;
}
F. 括号匹配
鈴虫の音さえ消えて
祭囃子も聞こえない
难点大概是想不到用网络流做吧。
限制很多,这种相互约束的东西要么拆或者简化约束,要么考虑用网络流来整体解决。 怎么用网络流在一个括号序列中找到一个合法括号串?从 向所有左括号连一条流量为 的边,所有右括号向 连一条边,那么最大流中,找到那些有流量的边就可以得到最长的一个括号子序列了。
从而,这个题三个只能留一个的限制只要新建一个虚点 分别连向这三个点。 到 容量为 ,而 分别向这三个点连一条容量为 的边。
现在再加上权值的限制,容易在边权上加入费用的限制,跑最大费用最大流即可。
/*
* Copyright© 2024 Ackerlanna & Heratino. All rights reserved.
* author: Ackerlanna & Heratino
* Problem: 【牛客练习赛122】 F
* Tag: 网络流
* Memory Limit: 512MiB
* Time Limit: 1000ms
* Source: 牛客练习赛122
*/
// Narcissu / どうか安寧な記憶を
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 2200 * 4;
constexpr int M = 4 * N;
constexpr int inf = 0x3f3f3f3f;
struct edge {
int flow, cap;
int cost;
int u, v;
} E[M];
std::basic_string<int> G[N];
int now[N];
int idx = 0;
inline void add(int u, int v, int cap, int cost) {
E[idx] = {0, cap, cost, u, v};
G[u] += idx++;
E[idx] = {0, 0, -cost, v, u};
G[v] += idx++;
}
int s, t;
int dis[N], dep[N];
bool inq[N];
bool SPFA() {
memset(dis, 0x3f, sizeof(dis));
memset(inq, 0, sizeof(inq));
std::queue<int> q;
q.push(s), inq[s] = 1;
dis[s] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for (const int &i : G[u]) {
const auto &e = E[i];
if (e.cap == e.flow)
continue;
if (dis[e.v] > dis[e.u] + e.cost) {
dis[e.v] = dis[e.u] + e.cost;
if (!inq[e.v])
q.push(e.v), inq[e.v] = true;
}
}
}
return dis[t] < inf;
}
bool bfs() {
memset(dep, 0x3f, sizeof(dep));
std::queue<int> q;
q.push(s);
dep[s] = 0;
while (!q.empty()) {
int u = q.front();
now[u] = 0;
q.pop();
for (const int &i : G[u]) {
const auto &e = E[i];
if (e.cap == e.flow)
continue;
if (dis[e.v] == dis[e.u] + e.cost && dep[e.v] == inf) {
dep[e.v] = dep[e.u] + 1;
q.push(e.v);
if (e.v == t)
return true;
}
}
}
return false;
}
int FLOW;
i64 COST;
int global;
int dfs(int u, i64 res) {
if (u == t || !res)
return res;
int flow = 0;
for (int &i = now[u]; i < G[u].size() && res; i++) {
auto &edg = E[G[u][i]];
auto &rev = E[G[u][i] ^ 1];
if (edg.cap > edg.flow && dep[edg.v] == dep[edg.u] + 1 && dis[edg.v] == dis[edg.u] + edg.cost) {
int k = dfs(edg.v, std::min(0ll + edg.cap - edg.flow, res));
if (!k) {
dep[edg.v] = inf;
continue;
}
edg.flow += k;
rev.flow -= k;
flow += k;
res -= k;
global += edg.cost;
}
}
return flow;
}
int n, m;
std::string str;
int weight[N];
std::vector<int> in;
std::vector<int> out;
int tot;
bool vis[N];
// 確かな愛が視たいなら、音に成って今逃げ出して!
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
std::cin >> str;
str = ' ' + str;
for (int i = 1; i <= n; i++)
std::cin >> weight[i];
tot = 2 * n;
s = 2 * n + m + 1;
t = 2 * n + m + 2;
for (int i = 1; i <= n; i++) {
add(i, i + n, 1, -weight[i]);
}
for (int i = 1; i <= m; i++) {
tot++;
int x, y, z;
std::cin >> x >> y >> z;
if (str[x] == '(') {
add(s, tot, 1, 0);
add(tot, x, 1, 0);
add(tot, y, 1, 0);
add(tot, z, 1, 0);
vis[x] = vis[y] = vis[z] = true;
} else {
add(x + n, tot, 1, 0);
add(y + n, tot, 1, 0);
add(z + n, tot, 1, 0);
add(tot, t, 1, 0);
vis[x] = vis[y] = vis[z] = true;
}
}
tot = 2 * n + m + 2;
int lst = -1;
for (int i = n; i >= 1; i--) {
if (str[i] == ')') {
tot++;
add(tot, i, 1, 0);
if (lst != -1)
add(tot, lst, inf, 0);
lst = tot;
} else {
if (lst != -1)
add(i + n, lst, 1, 0);
}
}
for (int i = 1; i <= n; i++)
if (!vis[i]) {
if (str[i] == '(') {
add(s, i, 1, 0);
} else {
add(i + n, t, 1, 0);
}
}
i64 sum = 0;
for (int i = 1; i <= n; i++)
sum += weight[i];
while (SPFA()) {
global = 0;
bfs();
FLOW += dfs(s, inf);
COST += global;
}
bool flag = true;
for (auto i : G[s]) {
if (E[i].flow != E[i].cap) {
flag = false;
}
}
for (auto i : G[t]) {
if (E[i ^ 1].flow != E[i ^ 1].cap) {
flag = false;
}
}
if (flag) {
std::cout << "Yes\n";
std::cout << sum + COST << '\n';
} else {
std::cout << "No\n";
}
return 0;
}