【LG2495】[SDOI2011]消耗战

【LG2495】[SDOI2011]消耗战

题面

洛谷

题解

参考博客

题意

给你\(n\)个点的一棵树

\(m\)个询问,每个询问给出\(k\)个点

求将这\(k\)个点与\(1\)号点断掉的最小代价

其中\(n\leq250000\) \(m\geq1\) \(\Sigma k_i\leq500000\)

暴力

考虑直接暴力\(dp\)

\(dp[i]\)表示处理完\(i\)的子树的最小代价

\(dp[i]\)\(=\)\(min\Sigma dp[son_i],mn[i]\)其中\(mn[i]\)代表根节点到\(i\)节点路径上的最小边权

这样的话复杂度\(O(nm)\)无法通过此题

但观察数据范围

发现利用\(\Sigma k_i\)比较小的特点切入

虚树

思想

就是只将树上有用的点提取出来进行\(dp\),重构一棵树

这里指询问点和它们的\(lca\)

构建

考虑如何建一颗虚树。

首先我们要先对整棵树dfs一遍,求出他们的dfs序,然后对每个节点以dfs序为关键字从小到大排序

同时维护一个栈,表示从根到栈顶元素这条链。

设当前要加入的节点为\(p\),栈顶元素为\(x=s[top],lca\)为它们的最近公共祖先

因为我们按照\(dfs\)序遍历,所以\(p\)不可能是\(lca\)

那么现在会有两种情况

1.\(lca\)\(x\),直接将\(p\)入栈

2.\(x\)\(p\)位于不同的子树中,则此时\(x\)所在子树已经遍历完了,我们需对其进行构造

设栈顶元素为\(x\),第二个为\(y\)

\(dfn[y]>dfn[lca]\),可连边\(y->x\),将\(x\)出栈。

\(dfn[y]=dfn[lca]\)\(y\)=\(lca\),连边\(lca->x\),子树构建完毕。

\(dfn[y]<dfn[lca]\),即\(lca\)\(x\)\(y\)之间,连边\(lca->x\)\(x\)出栈,再将\(lca\)入栈,子树构建完毕。

然后重复这个过程就可以了,不理解的话可以自己手玩一下。。。

复杂度

大约是\(O(\Sigma k_i的)\),可能会带一些小常数

然后这题的代码贴在这里了:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <climits>
#include <vector> 
using namespace std;

inline int gi() {
    register int data = 0, w = 1;
    register char ch = 0;
    while (ch != '-' && (ch > '9' || ch < '0')) ch = getchar();
    if (ch == '-') w = -1 , ch = getchar();
    while (ch >= '0' && ch <= '9') data = data * 10 + (ch ^ 48), ch = getchar();
    return w * data;
}
typedef long long ll;
#define int ll 
const int MAX_N = 250005;
const int MAX_LOG_N = 19; 
struct Graph { int to, cost, next; } e[MAX_N << 1]; int fir[MAX_N], e_cnt;
void clearGraph() { memset(fir, -1, sizeof(fir)); e_cnt = 0; } 
void Add_Edge(int u, int v, int w) { e[e_cnt] = (Graph){v, w, fir[u]}; fir[u] = e_cnt++; } 
int N, M, s[MAX_N], _top, dfn[MAX_N], mn[MAX_N]; 
namespace cpp1 {
	int dep[MAX_N], top[MAX_N], fa[MAX_N], size[MAX_N], son[MAX_N], tim; 
	void dfs1(int x) {
		dep[x] = dep[fa[x]] + 1; size[x] = 1; 
		for (int i = fir[x]; ~i; i = e[i].next) {
			int v = e[i].to; if (v == fa[x]) continue; 
			fa[v] = x; mn[v] = min(mn[x], e[i].cost); 
			dfs1(v);
			size[x] += size[v];
			if (size[v] > size[son[x]]) son[x] = v; 
		} 
	}
	void dfs2(int x, int tp) {
		top[x] = tp; dfn[x] = ++tim;
		if (son[x]) dfs2(son[x], tp); 
		for (int i = fir[x]; ~i; i = e[i].next) {
			int v = e[i].to;
			if (v == fa[x] || v == son[x]) continue;
			dfs2(v, v); 
		} 
	}
	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]]; 
		}
		return dep[x] > dep[y] ? y : x; 
	} 
}
namespace cpp2 {
	vector<int> G[MAX_N];
	void add(int x, int y) { G[x].push_back(y); }
	void ins(int x) { 
		if (_top == 1) { s[++_top] = x; return ; }
		int lca = cpp1::LCA(x, s[_top]);
		if (lca == s[_top]) return ;
        while (_top > 1 && dfn[s[_top - 1]] >= dfn[lca]) add(s[_top - 1], s[_top]), _top--; 
		if (lca != s[_top]) add(lca, s[_top]), s[_top] = lca;
		s[++_top] = x; 
	} 
	ll dfs(int x) {
		if (G[x].size() == 0) return mn[x]; 
		ll res = 0;
		for (int i = 0; i < (int)G[x].size(); i++) res += dfs(G[x][i]); 
		G[x].clear();
		return min(res, 1ll * mn[x]); 
	}
	bool cmp(int a, int b) { return dfn[a] < dfn[b]; } 
}
int a[MAX_N];
#undef int 
int main () {
	#define int ll 
	clearGraph(); 
	N = gi(); 
	for (int i = 1; i < N; i++) {
		int u = gi(), v = gi(), w = gi(); 
		Add_Edge(u, v, w); 
		Add_Edge(v, u, w); 
	}
	fill(&mn[1], &mn[N + 1], LLONG_MAX / 2); 
	cpp1::dfs1(1); cpp1::dfs2(1, 1);
	M = gi(); 
	while (M--) {
		int K = gi(); for (int i = 1; i <= K; i++) a[i] = gi(); 
		sort(&a[1], &a[K + 1], cpp2::cmp);
		s[_top = 1] = 1;
		for (int i = 1; i <= K; i++) cpp2::ins(a[i]); 
		while (_top > 0) cpp2::add(s[_top - 1], s[_top]), _top--;
		printf("%lld\n", cpp2::dfs(1)); 
	} 
    return 0; 
} 
posted @ 2018-12-18 21:30  heyujun  阅读(260)  评论(0编辑  收藏  举报