题解:AT_abc450_f [ABC450F] Strongly Connected 2
Jasonbenji · · 题解
ABC450F 题解
什么?图论题?好难啊!
现将问题进行转化。当且仅当能够从
:::success[证明]
如果从
如果从
从
从
于是这个题就一点都不图论了。记
方法一:直接 DP + 线段树优化
现在我们要按照一定顺序考虑这些边,使得每一条这种通路都能按照顺序被考虑每条边。显然按照起点排序即可。
:::warning[如何排序]
其实你只需要保证
除了按照起点排序、终点排序,你甚至可以按照
所以说,在考虑过前
于是考虑设计状态
对于
对于
对于
对于
发现实际上由
:::success[code]
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2e5;
constexpr int M = 2e5;
constexpr int P = 998244353;
int n, m, x[M + 1], y[M + 1];
int id[M + 1];
int sum[N * 4 + 1], tag[N * 4 + 1];
inline void set_tag(int id, int tg) {
sum[id] = sum[id] * 1LL * tg % P;
tag[id] = tag[id] * 1LL * tg % P;
}
inline void push_down(int id) {
if (tag[id] == 1)
return;
set_tag(id * 2, tag[id]);
set_tag(id * 2 + 1, tag[id]);
tag[id] = 1;
}
inline void update(int id) {
sum[id] = (sum[id * 2] + sum[id * 2 + 1]) % P;
}
inline void build(int id, int l, int r) {
tag[id] = 1;
if (l == r)
sum[id] = l == 1 ? 1 : 0;
else {
int mid = l + r >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
inline void change(int id, int l, int r, int q, int to) {
if (l == r)
sum[id] = to;
else {
int mid = l + r >> 1;
push_down(id);
if (q <= mid)
change(id * 2, l, mid, q, to);
else
change(id * 2 + 1, mid + 1, r, q, to);
update(id);
}
}
inline void modify(int id, int l, int r, int ql, int qr, int tg) {
if (ql > qr)
return;
else if (l == ql && r == qr)
set_tag(id, tg);
else {
int mid = l + r >> 1;
push_down(id);
if (qr <= mid)
modify(id * 2, l, mid, ql, qr, tg);
else if (ql > mid)
modify(id * 2 + 1, mid + 1, r, ql, qr, tg);
else {
modify(id * 2, l, mid, ql, mid, tg);
modify(id * 2 + 1, mid + 1, r, mid + 1, qr, tg);
}
update(id);
}
}
int query(int id, int l, int r, int ql, int qr) {
if (ql > qr)
return 0;
else if (l == ql && r == qr)
return sum[id];
else {
int mid = l + r >> 1;
push_down(id);
if (qr <= mid)
return query(id * 2, l, mid, ql, qr);
else if (ql > mid)
return query(id * 2 + 1, mid + 1, r, ql, qr);
else
return (query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr)) % P;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> x[i] >> y[i];
iota(id + 1, id + 1 + m, 1);
sort(id + 1, id + 1 + m, [](int u, int v) {
return x[u] < x[v];
});
build(1, 1, n);
for (int i = 1; i <= m; i++) {
int l = x[id[i]], r = y[id[i]];
modify(1, 1, n, 1, l - 1, 2);
modify(1, 1, n, r + 1, n, 2);
change(1, 1, n, r, (query(1, 1, n, r, r) + query(1, 1, n, l, r)) % P);
// cerr << i << ": " << l << ' ' << r << '\n';
// for (int j = 1; j <= n; j++)
// cerr << query(1, 1, n, j, j) << " \n"[j == n];
}
cout << query(1, 1, n, n, n) << '\n';
return 0;
}
:::
方法二:容斥 DP
先转化成线段问题。对于每个线段,我们先将
那正难则反,记
考虑如何计算
:::error[暴力 code]
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2e5;
constexpr int M = 2e5;
constexpr int P = 998244353;
int n, m, x[M + 1], y[M + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
y[i]--;
}
int ans = 0;
for (int i = 0; i < (1 << n - 1); i++) {
int cnt = 0;
for (int j = 1; j <= m; j++) {
bool flag = true;
for (int k = 1; k < n; k++)
if (i >> k - 1 & 1)
flag &= !(x[j] <= k && k <= y[j]);
cnt += flag;
}
int ret = 1;
for (int j = 1; j <= cnt; j++)
ret = (ret + ret) % P;
if (__builtin_popcount(i) & 1)
ans = (ans + P - ret) % P;
else
ans = (ans + ret) % P;
}
cout << ans << '\n';
return 0;
}
:::
之后考虑用容斥 DP 优化。我们走到哪里算到哪里,考虑
:::warning[DP code]
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2e5;
constexpr int M = 2e5;
constexpr int P = 998244353;
int n, m, x[M + 1], y[M + 1];
int dp[N + 1 + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
y[i]--;
}
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
int cnt = 1;
for (int k = 1; k <= m; k++)
if (j < x[k] && y[k] < i)
cnt = (cnt + cnt) % P;
dp[i] = (dp[i] + dp[j] * 1LL * cnt % P * (P - 1) % P) % P;
}
}
cout << (P - dp[n]) % P << '\n';
return 0;
}
:::
然后是最后的优化。显然随着
我们用一棵线段树,记录在每个
:::success[code]
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2e5;
constexpr int M = 2e5;
constexpr int P = 998244353;
int n, m, x[M + 1], y[M + 1];
vector<int> vec[N + 1];
int sum[N * 4 + 1], tag[N * 4 + 1], dp[N + 1];
inline void set_tag(int id, int tg) {
sum[id] = sum[id] * 1LL * tg % P;
tag[id] = tag[id] * 1LL * tg % P;
}
inline void push_down(int id) {
if (tag[id] == 1)
return;
set_tag(id * 2, tag[id]);
set_tag(id * 2 + 1, tag[id]);
tag[id] = 1;
}
inline void update(int id) {
sum[id] = (sum[id * 2] + sum[id * 2 + 1]) % P;
}
inline void build(int id, int l, int r) {
tag[id] = 1;
if (l == r)
sum[id] = l == 1 ? 1 : 0;
else {
int mid = l + r >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
inline void change(int id, int l, int r, int q, int to) {
if (l == r)
sum[id] = to;
else {
int mid = l + r >> 1;
push_down(id);
if (q <= mid)
change(id * 2, l, mid, q, to);
else
change(id * 2 + 1, mid + 1, r, q, to);
update(id);
}
}
inline void modify(int id, int l, int r, int ql, int qr, int tg) {
if (ql > qr)
return;
else if (l == ql && r == qr)
set_tag(id, tg);
else {
int mid = l + r >> 1;
push_down(id);
if (qr <= mid)
modify(id * 2, l, mid, ql, qr, tg);
else if (ql > mid)
modify(id * 2 + 1, mid + 1, r, ql, qr, tg);
else {
modify(id * 2, l, mid, ql, mid, tg);
modify(id * 2 + 1, mid + 1, r, mid + 1, qr, tg);
}
update(id);
}
}
int query(int id, int l, int r, int ql, int qr) {
if (ql > qr)
return 0;
else if (l == ql && r == qr)
return sum[id];
else {
int mid = l + r >> 1;
push_down(id);
if (qr <= mid)
return query(id * 2, l, mid, ql, qr);
else if (ql > mid)
return query(id * 2 + 1, mid + 1, r, ql, qr);
else
return (query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr)) % P;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
vec[--y[i]].push_back(x[i]);
}
build(1, 1, n);
for (int i = 1; i <= n; i++) {
dp[i] = (P - query(1, 1, n, 1, i)) % P;
if (i < n) {
change(1, 1, n, i + 1, dp[i]);
for (int j : vec[i])
modify(1, 1, n, 1, j, 2);
}
}
cout << (P - dp[n]) % P << '\n';
return 0;
}
:::
总结
本题中方法一无疑是最好的方法,但是大多数题,如果 DP 的状态就是
对于方法二,实际上在此题中有些大材小用。线段覆盖相关问题正着做其实是很困难的,因为在每个时刻需要记录大于
(于