引用 lwz_159的题解博客
首先贪心地想,能走到最多的点肯定能获得更多能量,但边权会是限制,在可以来回走的前提下,我们显然可以保留限制最小的边让这个图变成一颗树(最小生成树)
但仅仅有 最小生成树 并不能很好地处理 q 次询问的时间复杂度,这里我们再次引入 Kruskal重构树 来解决
什么是kruskal重构树?
类型题:洛谷:P2245 星际导航
在一颗 kruskal重构树 上,我们拥有神奇的性质:
拥有了这样一颗重构树,我们就能用倍增的方式每次处理查询
每次询问我们都会得到一个起始点(根据原则必定是叶子节点)和起始值,假设为图中的 a 点;在这颗重构树上我们每次尽可能往根节点跳,判断能不能跳过去就是判断 a点的值 是否大于等于 b点的值(边权限制)
一旦我们能跳到 b点,因为二叉堆的性质 b这条边 会是 b这个子树中最大的一条边,所以我们必定能得到 b子树中所有点的权值(最大的边都能走,其他小边肯定也能走过去)(这里的权值针对点)
跳的过程用倍增压缩,这样总时间就可以在 O ( q l o g n ) O(qlogn) O(qlogn) 内,一旦某次怎么都跳不上去我们就可以退出了(止步于此)
C o d e : Code: Code:
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define rfor(a,b,c) for(int a=b;a>=c;a--)
#define endl "\n"
//[博客地址]:https://blog.csdn.net/weixin_51797626?t=1
using namespace std;
inline void read(int& x) { x = 0; int f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = getchar(); } x *= f; }
void write(int x) { if (x < 0) putchar('-'), x = -x; if (x >= 10) write(x / 10); putchar(x % 10 + '0'); }
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
const int N = 200010, M = 100010, MM = N;
int INF = 0x3f3f3f3f, mod = 1e9 + 7;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
struct edge
{
int a, b, w;
bool operator <(const edge& ee)const { return w < ee.w; }
}ed[M];
vector<int> e[N];//重构树,重构树最多会比原最小生成树多一倍的点
int a[N], p[N], down[N];
ll d[N];
int fa[N][18];
int find(int x) {
return p[x] == x ? p[x] : p[x] = find(p[x]);
}
int kruskal() { //先创建最小生成树
sort(ed + 1, ed + 1 + m);
forr(i, 1, 2 * n)p[i] = i;
int cnt = n;
forr(i, 1, m) {
int a = find(ed[i].a), b = find(ed[i].b);
if (a ^ b) {
d[++cnt] = ed[i].w;//同时用并查集建重构树
p[a] = p[b] = cnt;
e[cnt].push_back(a);
e[cnt].push_back(b);
}
}
return cnt;
}
int build(int x) { //递归预处理倍增,队列也可,不容易爆栈
int sum = a[x];
for (auto j : e[x]) {
fa[j][0] = x;
for (int i = 1; i <= 17; i++)
fa[j][i] = fa[fa[j][i - 1]][i - 1];
sum += build(j);
}
down[x] = sum;//以及以某点为根的子树内所有点权和
return sum;
}
int main() {
cinios;
cin >> n >> m >> k;
forr(i, 1, n)cin >> a[i];
forr(i, 1, m) {
int a, b, x;
cin >> a >> b >> x;
ed[i] = { a,b,x };
}
n = kruskal();
build(n);
d[0] = 1e18;//边界要给个极值
forr(i, 1, k) {
int x, q;
cin >> x >> q;
ll ans = 1ll * a[x] + q;
while (x != n)//跳到根节点为止
{
int x1 = x;
for (int i = 17; i >= 0; i--)
if (d[fa[x][i]] <= ans)x = fa[x][i];//能跳我们就跳上去
if (x == x1)break;//如果某轮怎么都跳不动就退出
ans = 1ll * down[x] + q;
//随时更新携带的能量
}
cout << ans << '\n';
}
return 0;
}
/*
*/