2640. QYQ在艾泽拉斯
写在前面: 这道题目太恶心了!!!
一些约定
论 S T L STL STL
- 因为 xc 不允许我们使用 STLSTLSTL, 所以作者手打了一个 STL
代码
namespace STL { template <class T> inline void swap(T& a, T& b) { T t = a; a = b; b = t; }template <class T, class Cmp> inline void _qsort(T* data, int left, int right, Cmp q) {if (left >= right) return;T pivot = data[(left + right) / 2];swap(data[(left + right) / 2], data[left]);register int i = left + 1, j = right;while (i <= j) {while (i <= j && q(data[i], pivot)) i++;while (i <= j && q(pivot, data[j])) j--; if (i < j) swap(data[i], data[j]), i++, j--;else break;}swap(data[left], data[j]), _qsort(data, left, j - 1, q), _qsort(data, j + 1, right, q);}template <class T, class Cmp> inline void sort(T* begin, T* end, Cmp q) { _qsort(begin, 0, (int)(end - begin) - 1, q); }template <class T>class vector {private:T* data; // data valint end_1, end_2; // two end static constexpr int P = 100; // 阈值 void __resize(int __size) { // 内置 resize 扩展 end_2 空间T* __new_vec = new T[end_2 = __size];for (int ___i = 0; ___i < end_1; ___i++) __new_vec[___i] = data[___i]; // 拷贝delete data;data = __new_vec; }void __new(int __size) {__resize(__size + P);}public:vector<T>() {data = NULL; end_1 = end_2 = 0; __new(end_2);}vector<T>(int size, T start_val = T()) {__new(size); end_1 = size;for (int __II = 0; __II < size; __II++) data[__II] = start_val;}void push_back(T q) {if (end_1 + 1 == end_2) __new(end_2); // 扩容data[end_1++] = q; }void pop_back() {end_1--;}bool empty() const {return end_1 == 0;}int size() const {return end_1;}T& operator[](unsigned long long q) {return data[q];}T& operator[](unsigned long long q) const {return data[q];}T* begin() {return data;}T* end() {return data + end_1;}void resize(int __n) {__new(__n);}};template <class T> struct greater {bool operator()(const T& a, const T& b) {return a > b;}};template <class T> struct less {bool operator()(const T& a, const T& b) {return a < b;}};
}
使用教程
- 首先在代码前面新加上一行:
using namespace STL;
表示加入STL
这个命名空间(可能你不懂,但是只要会用就可以)
或者在调用函数的时候(这里比如调用sort
) 这样写STL::sort(a, a+n, cmp);
比如下面的代码
// 上面的STL的内容
int Sort[100];
STL::sort(Sort, Sort + n);// 或者
using namespace STL;
int Sort[100];
sort(Sort, Sort + n);
sort
函数的使用是和STL
里面的sort
是一样的
什么都可以排序,可以传入一个比较函数,仿函数,重载小于运算符
Tips: 这个版本是不可以使用小于运算符、重载运算符的,要可以的版本叫爸爸- 里面有
greater
和less
这两个用法也是一样的
重点 vector 的使用
低级用法
和 STL
里面的 vector
是一致的, 循环的简化(for(int u : vec)
) 也是支持的
高级用法
代码里面有这样的一行
static constexpr int P = 100; // 阈值
阈值是什么?
就是说, vector
满了之后的单次扩展量
比如你要定义一个图 G
空间的限制是 256MB
时
vector<int> G[100000] // 5个0
那么你可以提高速度 换句话说,阈值越大速度越快(快的还不止一点)
将阈值改为 200
因为 100000×200=2×102×105=2×107100000 \times 200=2\times10^2\times10^5=2\times10^7100000×200=2×102×105=2×107
换算一下空间
2×107个int=2×107×4B=8×107B2\times10^7个int=2\times10^7\times4B=8\times10^7B2×107个int=2×107×4B=8×107B
换成 KB
8×107÷1024=78125KB8\times10^7\div1024=78125KB8×107÷1024=78125KB
换成 MB
78125÷1024=76.29MB78125\div1024=76.29MB78125÷1024=76.29MB
空间 76.29MB<256MB76.29MB<256MB76.29MB<256MB 可以开
注意: 上面的 76.29MB 不一定是这个数组的真实空间,可能这个空间会更大,请注意MLE的问题,适当优化阈值
思路(崩溃的心路历程)
1. 使用 Kosaraju 算法进行缩点,将一个强连通分量变成一个点
- 强连通分量就是有一个有向图,这个图删除任意一条边都可以连通
- 连通就是这个图里面的任意一个节点都可以到达其他所有节点
- 既然这个强连通分量是两两可以到达的,我们就将这个图变成 1 个点,这个点的价值就是这个强连通分量上的点的
v
之和
这个操作是绝对不会影响答案的,简单的证明如下
- 首先这个强连通分量上面的城市一定是可以两两连通的(强连通分量的定义)
- 根据条件 1,这些城市一定是在同一个岛屿上面
- QyQ 同学是可以在一个岛屿 上面任意走的,而
v
都是正数,所以 QyQ 同学在同一个岛屿上走到的城市越多越好。
根据条件 2,可以推得条件 3 成立,条件 3 成立说明如果你走到了这个强连通分量上面的一个点,那么最优的方法一定是要走完这个强连通分量,所以上面的缩点是成立的!
代码
namespace SolveLink {int s[N], s_tot = 0, color[N], tot, vis[N];star_t<N, M << 1> zheng, fan; // 正反图uf qwq;void calc() {function<void(int)> dfs1 = [&](int u) -> void {vis[u] = 1;for (int i = zheng.head[u]; i; i = zheng.next[i]) if (!vis[zheng.to[i]]) dfs1(zheng.to[i]);s[++s_tot] = u;};function<void(int)> dfs2 = [&](int u) -> void {color[u] = tot; new_v[tot] += v[u];for (int i = fan.head[u]; i; i = fan.next[i]) if (!color[fan.to[i]]) dfs2(fan.to[i]); };REP(i, 1, n) if (!vis[i]) dfs1(i);rREP(i, n, 1) if (!color[s[i]]) ++tot, dfs2(s[i]);}void main() {calc();REP(from, 1, n) for (int i = zheng.head[from]; i; i = zheng.next[i]) {int to = zheng.to[i];if (color[from] == color[to]) continue; G.add(color[from], color[to]);}}
}
2. 使用并查集维护连通性
因为题目很明确的告诉我们,当两个点在同一个岛屿的条件是这两个点之间的边看成无向边之后是一个连通图
很显然,可以使用并查集计算在同一个岛屿上的城市
不写代码
除非叫爸爸
3. 对于每一个岛屿,使用 DAG 上面的 dp 计算
- DAG 是有向无环图
- 直接 dp 即可
时间复杂度
步骤 1 的时间复杂度是 O(n+m)O(n+m)O(n+m)
步骤 2 的时间复杂度是 O(n+m)O(n+m)O(n+m) 不考虑并查集的时间复杂度
步骤 3 的时间复杂度是 O(n+m)O(n + m)O(n+m)
综上所述,时间复杂度为 O(n+m)O(n+m)O(n+m) 只是常数太大了 oj 上面跑出了 82ms
代码
还是我良心
#include <stdio.h>
#include <ctype.h>
#include <functional>using std::function;#define reg register
#define FOR(i, a, b) for (reg int i = (a); i < (b); i++)
#define REP(i, a, b) for (reg int i = (a); i <= (b); i++)
#define rFOR(i, a, b) for (reg int i = (a); i > (b); i--)
#define rREP(i, a, b) for (reg int i = (a); i >= (b); i--)
#define Pq N
constexpr int N = 100010, M = 1000010;namespace fast_io
{// 数组下标从 0 开始char buf[1 << 23], *p1 = buf, *p2 = buf;#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)int rd() {reg char c = getchar(); reg int ret = 0;while (!isdigit(c)) c = getchar();do {ret = (ret << 3) + (ret << 1) + (c ^ 48); c = getchar();} while (isdigit(c));return ret;}char dig[18];void wt(int x) {reg int p = !x;while (x) dig[p++] = x % 10, x /= 10;while (p--) putchar(dig[p] + '0');}
}
namespace STL { template <class T> inline void swap(T& a, T& b) { T t = a; a = b; b = t; }template <class T, class Cmp> inline void _qsort(T* data, int left, int right, Cmp q) {if (left >= right) return;T pivot = data[(left + right) / 2];swap(data[(left + right) / 2], data[left]);register int i = left + 1, j = right;while (i <= j) {while (i <= j && q(data[i], pivot)) i++;while (i <= j && q(pivot, data[j])) j--; if (i < j) swap(data[i], data[j]), i++, j--;else break;}swap(data[left], data[j]), _qsort(data, left, j - 1, q), _qsort(data, j + 1, right, q);}template <class T, class Cmp> inline void sort(T* begin, T* end, Cmp q) { _qsort(begin, 0, (int)(end - begin) - 1, q); }template <class T>class vector {private:T* data; // data valint end_1, end_2; // two end static constexpr int P = 100; // 阈值 void __resize(int __size) { // 内置 resize 扩展 end_2 空间T* __new_vec = new T[end_2 = __size];for (int ___i = 0; ___i < end_1; ___i++) __new_vec[___i] = data[___i]; // 拷贝delete data;data = __new_vec; }void __new(int __size) {__resize(__size + P);}public:vector<T>() {data = NULL; end_1 = end_2 = 0; __new(end_2);}vector<T>(int size, T start_val = T()) {__new(size); end_1 = size;for (int __II = 0; __II < size; __II++) data[__II] = start_val;}void push_back(T q) {if (end_1 + 1 == end_2) __new(end_2); // 扩容data[end_1++] = q; }void pop_back() {end_1--;}bool empty() const {return end_1 == 0;}int size() const {return end_1;}T& operator[](unsigned long long q) {return data[q];}T& operator[](unsigned long long q) const {return data[q];}T* begin() {return data;}T* end() {return data + end_1;}void resize(int __n) {__new(__n);}};template <class T> struct greater {bool operator()(const T& a, const T& b) {return a > b;}};template <class T> struct less {bool operator()(const T& a, const T& b) {return a < b;}};
}
#define rd() fast_io::rd()
#define wt(Qj) fast_io::wt(Qj) // qjtemplate <int MAXN, int MAXM>
struct star_t {int head[MAXN], next[MAXM], to[MAXM], tot;star_t() : tot(0) {};void add(int x, int y) {next[++tot] = head[x]; head[x] = tot; to[tot] = y;}
};int n, m, v[N], k;
int new_v[N]; // 新的点的点权struct uf {int fa[N];void Init() {FOR(i, 0, N) fa[i] = i;}int find(int x) {return (fa[x] == x) ? x : fa[x] = find(fa[x]);}int merge(int x, int y) {return (x = find(x)) != (y = find(y)) ? fa[x] = y : 0;}
};
star_t<N, M << 1> G;
namespace SolveLink {int s[N], s_tot = 0, color[N], tot, vis[N];star_t<N, M << 1> zheng, fan; // 正反图uf qwq;void calc() {function<int(int)> dfs1 = [&](int u) -> int {vis[u] = 1;for (int i = zheng.head[u]; i; i = zheng.next[i]) if (!vis[zheng.to[i]]) dfs1(zheng.to[i]);s[++s_tot] = u;};function<int(int)> dfs2 = [&](int u) -> int {color[u] = tot; new_v[tot] += v[u];for (int i = fan.head[u]; i; i = fan.next[i]) if (!color[fan.to[i]]) dfs2(fan.to[i]); };REP(i, 1, n) if (!vis[i]) dfs1(i);rREP(i, n, 1) if (!color[s[i]]) ++tot, dfs2(s[i]);}void main() {calc();REP(from, 1, n) for (int i = zheng.head[from]; i; i = zheng.next[i]) {int to = zheng.to[i];if (color[from] == color[to]) continue; G.add(color[from], color[to]);}}
}
namespace DAG_n {uf qwq;STL::vector<int> DAG[N]; // 计算 DAGint vis[N], tot = 0, dp[N] = {0}, Sort[N] = {0};int max(int a, int b) {return a > b ? a : b;}void main() {qwq.Init(); // 初始化并查集n = SolveLink::tot;REP(from, 1, n) for (int i = G.head[from]; i; i = G.next[i]) {int to = G.to[i];qwq.merge(from, to);}REP(i, 1, n) {if (!vis[qwq.find(i)]) vis[qwq.find(i)] = ++tot;int p = i;DAG[vis[qwq.find(i)]].push_back(p);}auto calc = [&](int idx) -> int { // 计算 DAGfor (int from : DAG[idx]) dp[from] = new_v[from];for (int from : DAG[idx]) for (int i = G.head[from]; i; i = G.next[i]) {int to = G.to[i];dp[to] = max(dp[to], dp[from] + new_v[to]);}int res = 0;for (int from : DAG[idx]) res = max(res, dp[from]);return res; };REP(i, 1, tot) Sort[i] = calc(i);STL::sort(Sort + 1, Sort + tot + 1, STL::greater<int>());int ans = 0; for (reg int i = 1; i <= tot && i <= k + 1; i++) ans += Sort[i];printf("%d", ans);}
}int main()
{//freopen("qwq.in", "r", stdin);freopen("azeroth.in", "r", stdin);freopen("azeroth.out", "w", stdout);n = rd(), m = rd();REP(i, 1, m) {int from = rd(), to = rd();if (from == to) continue;SolveLink::zheng.add(from, to), SolveLink::fan.add(to, from);}REP(i, 1, n) v[i] = rd();k = rd();SolveLink::main();DAG_n::main();
// REP(i, 1, n) printf("%d=%d\n", i, SolveLink::color[i]);
// for (int i = 1; i <= DAG_n::tot; i++) {
// for (int u : DAG_n::DAG[i]) printf("%d ", u);
// putchar('\n');
// }
// printf("Link=\n");
// for (int i = 1; i <= n; i++) for (int j = G.head[i]; j; j = G.next[j]) {
// printf("%d->%d\n", i, G.to[j]);
// }return Pq;
}
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
向下看
你的代码是不是 RE 了?
你是不是已经注意到 Pq 的问题了?
但是代码还是AC不了?
因为我最讨厌ctj的人…