消耗战
题目地址:
基本思路:
先补充一下数据范围:
,
,
我们发现如果没有次的查询单单是询问一次,那么设
为切断
这颗子树下所有点的最小代价,
为
到
号点的路径中最小的边权,我们可以很容易的得到一个树形
的转移方程:
但是我们肯定不能对每次的查询暴力跑,我们观察到
,似乎可以从这里入手,因此我们引入虚树的概念。
虚树,实际上就是对于每次查询的点,我们只针对这些有用的点建树,而忽略那些用不到的点,对于虚树的构建我们可以参考下面的步骤:
首先我们要将每次查询的点,根据序进行排序;
然后我们维护一个栈,栈底到栈顶的元素表示虚树中从上到下的一条链,
在这里我们先固定放进根节点,对于每次插入的节点
,
当栈中只有一个元素的时候我们直接将插入栈,
否则我们令,
当,那么意味着
在栈顶节点的子树中,那么直接将
压入栈中,
如果,那么说明
和
分属
的两棵不同的子树,而且
所在的子树中已经构建完成了,这时候我们弹栈并且在弹栈过程中连边,直到
或
时停止,这时再判断
是否等于
:
若不等,先从向
连边,然后弹出栈顶,压入
,再压入
,
否则直接压入。
具体过程大家可以根据代码运行过程,画图理解,我不太会用绘图软件,这里就不引入图片了。
这样我们对于每次查询都构建一棵虚树,然后再在虚树上跑前面的树形过程,那么就能在规定时间里得到答案,虚树的复杂度只与
有关为
。
参考代码如下,代码中使用树剖求。
参考代码:
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, l, r) for (int i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define debug(x) cerr << #x << " = " << x << '\n';
#define pll pair <ll, ll>
#define fir first
#define sec second
#define INF 0x3f3f3f3f
namespace IO {
const int SIZE = 1 << 20;
char buf[SIZE], *p1 = buf, *p2 = buf;
char obuf[SIZE], *O = obuf;
int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, SIZE, stdin), p1 == p2) ? EOF : *p1++;
}
int read() {
int x = 0, f = 0;
char ch = getc();
while (!isdigit(ch)) f |= ch == '-', ch = getc();
while (isdigit(ch)) x = 10 * x + ch - '0', ch = getc();
return f ? -x : x;
}
template<typename T> void print(T x) {
if (x < 0) *O++ = '-', x = -x;
if (x > 9) print(x / 10);
*O++ = char(x % 10 + '0');
}
template<typename T> void print(T x, char opt) {
print(x), *O++ = opt;
}
void Flush() {
fwrite(obuf, O - obuf, 1, stdout);
}
}
using namespace IO;
const int maxn = 5e5 + 10;
struct Edge{
int to,w,next;
}edge[maxn << 1];
int cnt = 0,head[maxn];
void init() {
cnt = 0;
mset(head, -1);
}
void add_edge(int u,int v,int w) {
edge[++cnt].next = head[u];
edge[cnt].w = w;
edge[cnt].to = v;
head[u] = cnt;
}
int sz[maxn],dep[maxn],son[maxn],fa[maxn];
ll mn[maxn];
int dfn[maxn],top[maxn],tot;
void dfs1(int u,int par,int deep){
dep[u] = deep; fa[u] = par; sz[u] = 1; son[u] = 0;
for(int i = head[u] ; i != -1 ; i = edge[i].next){
int to = edge[i].to;
if(to == par) continue;
mn[to] = min(mn[u], (ll)edge[i].w);
dfs1(to,u,deep + 1);
sz[u] += sz[to];
if(sz[to] > sz[son[u]]) son[u] = to;
}
}
void dfs2(int u,int tp) {
dfn[u] = ++tot;
top[u] = tp;
if (son[u]) dfs2(son[u], tp);
for (int i = head[u]; i != -1; i = edge[i].next) {
int to = edge[i].to;
if (to == fa[u] || to == son[u]) continue;
dfs2(to, to);
}
}
int LCA(int x,int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
return y;
}
vector<int> G[maxn];
int stk[maxn],tp;
void add(int x,int y){
G[x].push_back(y);
}
void insert(int x) {
if (tp == 1) { stk[++tp] = x; return; }
int lca = LCA(stk[tp], x);
if (lca == stk[tp]) return;
while (tp > 1 && dfn[stk[tp - 1]] >= dfn[lca]) add(stk[tp - 1],stk[tp]),tp--;
if (lca != stk[tp]) add(lca,stk[tp]),stk[tp] = lca;
stk[++tp] = x;
}
ll calc(int u){
if(SZ(G[u]) == 0) return mn[u];
ll res = 0;
for(auto to : G[u]) res += calc(to);
G[u].clear();
return min(mn[u],res);
}
int n,m,k,a[maxn];
inline bool cmp(int x,int y){
return dfn[x] < dfn[y];
}
signed main() {
n = read();
init();
rep(i,1,n - 1) {
int u = read(), v = read(), w = read();
add_edge(u, v, w);
add_edge(v, u, w);
}
mn[1] = (1ll << 60);
dfs1(1,0,1);
dfs2(1,1);
m = read();
while (m--) {
k = read();
rep(i, 1, k) a[i] = read();
sort(a + 1, a + 1 + k, cmp);
stk[tp = 1] = 1;
rep(i, 1, k) insert(a[i]);
while (tp > 0) add(stk[tp - 1], stk[tp]), tp--;
print(calc(1),'\n');
}
Flush();
return 0;
}
京公网安备 11010502036488号