首页 > 其他分享 >P2495 [SDOI2011] 消耗战

P2495 [SDOI2011] 消耗战

时间:2024-04-06 16:47:03浏览次数:26  
标签:rt std anc int top st SDOI2011 消耗战 P2495

P2495 [SDOI2011] 消耗战

虚树优化 dp 模板题

考虑 \(m=1\)。只需要简单的树形 dp,设 \(f_i\) 表示 \(i\) 子树中的关键点都到不了 \(i\) 点的最小代价。转移枚举子节点 \(v\),有:

若 \(v\) 点为关键点,\(f_u=f_u+w(u,v)\)。

否则,\(f_u=f_u+\min(f_v,w(u,v))\)。

如果每次询问都跑一遍,复杂度 \(O(nm)\)。考虑优化。

我们发现这题最关键的一点是,我们转移时访问的点很多都是无用的。事实上,我们只需要保存关键点以及关键点的 \(lca\) 即可转移。所以我们需要建出一棵新树满足这样的要求。

虚树,在原树中保留关键点以及两两的公共祖先和树根所构成的树。如何建出虚树?先将关键点按 \(dfs\) 序从小到大排序。我们考虑不断用栈维护一条最右链,当枚举一个关键点 \(v\) 时,求出 \(rt=lca(s[top],v)\),有以下情况:

若 \(rt=s[top]\),说明 \(v\) 在当前最右链上,直接将 \(v\) 插入栈即可。

否则,考虑一直弹栈直到没有点的深度大于 \(rt\),弹栈的同时连边,最后再插入 \(v\)。这部分细节多,这里只是概述简要思想,具体要用图才能说清楚。下图为一般情况。

到现在,建立虚树的代码呼之欲出。

void build() {
	std::sort(a + 1, a + k + 1, cmp); //按 dfs 序排序
	st[++top] = 1;
	for(int i = 1; i <= k; i++) {
		int rt = lca(a[i], st[top]);
		while(top && dep[st[top - 1]] >= dep[rt]) {
			add(st[top - 1], st[top]), top--;
		} //弹栈时连边
		if(st[top] != rt) {
			add(rt, st[top]), st[top] = rt;
		} //特殊情况,rt 为新点,连边后覆盖 st[top]
		st[++top] = a[i]; //最后插入 v
	}
	while(top > 1) {
		add(st[top - 1], st[top]);
		top--;
	} //最后连上最右链
	top = 0;
}

在这题里,虚树的边显然是路径上的最小值,倍增预处理即可。

建好虚树后在虚树上跑树形 dp 即可。总复杂度是 \(O(\sum k\log \sum k)=O(n\log n)\)。

#include <bits/stdc++.h>
#define pii std::pair<int, i64>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 250010;
int n, m, k, tot;
int a[N];
int anc[N][20], dfn[N], dep[N];
i64 mn[N][20];
std::vector<pii> V[N];
void dfs(int u, int fa) {
	anc[u][0] = fa;
	dfn[u] = ++tot;
	dep[u] = dep[fa] + 1;
	for(int j = 1; j <= 19; j++) {
		anc[u][j] = anc[anc[u][j - 1]][j - 1];
		mn[u][j] = std::min(mn[u][j - 1], mn[anc[u][j - 1]][j - 1]);
	}
	for(auto v : V[u]) {
		if(v.fi == fa) continue;
		mn[v.fi][0] = v.se;
		dfs(v.fi, u);
	}
}
int lca(int u, int v) {
	if(dep[u] < dep[v]) std::swap(u, v);
	for(int i = 19; i >= 0; i--) if(dep[anc[u][i]] >= dep[v]) u = anc[u][i];
	if(u == v) return u;
	for(int i = 19; i >= 0; i--) if(anc[u][i] != anc[v][i]) u = anc[u][i], v = anc[v][i];
	return anc[u][0];
}
int cnt;
int h[N];
struct node {
	int to, nxt;
	i64 w;
} e[N << 1];
void add(int u, int v, i64 w) {
	e[++cnt].to = v, e[cnt].nxt = h[u], e[cnt].w = w;
	h[u] = cnt;
}
bool cmp(int a, int b) {
	return dfn[a] < dfn[b];
}
i64 calc(int u, int v) {
	i64 ret = linf;
	for(int i = 19; i >= 0; i--) {
		if(dep[anc[u][i]] > dep[v]) ret = std::min(ret, mn[u][i]), u = anc[u][i]; 
	}
	return std::min(ret, mn[u][0]);
}
int st[N], top;
void build() {
	std::sort(a + 1, a + k + 1, cmp);
	st[++top] = 1;
	for(int i = 1; i <= k; i++) {
		int rt = lca(a[i], st[top]);
		while(top && dep[st[top - 1]] >= dep[rt]) {
			i64 dis = calc(st[top], st[top - 1]);
			add(st[top - 1], st[top], dis), top--;
		}
		if(st[top] != rt) {
			i64 dis = calc(st[top], rt);
			add(rt, st[top], dis), st[top] = rt;
		}
		st[++top] = a[i];
	}
	while(top > 1) {
		i64 dis = calc(st[top], st[top - 1]);
		add(st[top - 1], st[top], dis);
		top--;
	}
	top = 0;
}
bool vis[N];
i64 f[N];
void dp(int u, int fa) {
	for(int i = h[u]; i; i = e[i].nxt) {
		int v = e[i].to; i64 w = e[i].w;
		if(v == fa) continue;
		dp(v, u);
		if(vis[v]) f[u] += w;
		else f[u] += std::min(f[v], w);
		f[v] = 0, vis[v] = 0;
	}
	h[u] = 0;
}
void Solve() {
	std::cin >> n;
	for(int i = 1; i < n; i++) {
		int u, v, w;
		std::cin >> u >> v >> w;
		V[u].pb({v, w}), V[v].pb({u, w});
	}
	dfs(1, 0);
	std::cin >> m;
	while(m--) {
		std::cin >> k;
		for(int i = 1; i <= k; i++) {
			std::cin >> a[i];
			vis[a[i]] = 1;
		}
		build();
		dp(1, 0);
		std::cout << f[1] << "\n"; f[1] = 0;
		cnt = 0;
	}
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}

标签:rt,std,anc,int,top,st,SDOI2011,消耗战,P2495
From: https://www.cnblogs.com/FireRaku/p/18117546

相关文章

  • P2487 [SDOI2011] 拦截导弹 题解
    题目链接:拦截导弹约定:本题中提到的\(LDS\)和\(LIS\)不是严格单调,而是非严格单调,即为\(\le或者\ge\)。比较神奇的题,问的东西比较多,我们逐渐拆分:对于第一个问题而言,这个dp方程是很好写的:\[dp[i]=\max{dp[j]}+1(i<j,h[i]\leh[j],v[i]\lev[j])\]其中\(dp[i]\)即......
  • P2495 [SDOI2011] 消耗战
    题意给定一棵有边权的无根树。\(q\)次询问,每次询问\(k\)个点。求断边使得根节点\(1\)与\(k\)个点不连通的最小边权。Sol虚树。\(n^2\)dp是trivial的。考虑优化。注意到其中很多点都是无用的。考虑保留有效点。不难发现,有效点集为询问点两两\(lca\)的集合......
  • P2486 [SDOI2011] 染色
    题目描述给定一棵\(n\)个节点的无根树,共有\(m\)个操作,操作分为两种:将节点\(a\)到节点\(b\)的路径上的所有点(包括\(a\)和\(b\))都染成颜色\(c\)。询问节点\(a\)到节点\(b\)的路径上的颜色段数量。颜色段的定义是极长的连续相同颜色被认为是一段。例如112221......
  • 解题报告P2486 [SDOI2011] 染色
    P2486[SDOI2011]染色题目链接分两段,最后靠同一条重链合树剖加线段树,典中典。这题的线段树维护比较新颖。线段树中维护这个区间左右端点的颜色和颜色段数量。建树和查询和修改时要判断左区间的右端点和右区间的左端点是否颜色相同。如果不相同,直接将段数相加,否则减一。然......
  • 「SDOI2011」 黑白棋
    绷不住了,洛谷上的dp没一个表述清楚了,一怒之下写一篇题解。注意本题解只讲dp部分。首先转化不合法的充要条件就是:设相邻两个棋子中间间隔数量为\(b\),那么对于任意非负整数\(i\)都有\((d+1)|\sum(b\&2^i)\)。其中\(\&\)是按位与运算。所以我们要计数一个有序的并且包含......
  • P2486 [SDOI2011] 染色 题解
    P2486[SDOI2011]染色神仙树剖题。题意给你一棵树,每个点都有颜色,支持下面两种操作:路径染色。路径颜色段数量查询。树剖部分我们看到树上问题,不好处理,所以想办法给他树剖搞一搞,给他转化成序列的操作。我们树剖就是正常的树剖,然后我们考虑如何维护这个颜色序列。我......
  • 「SDOI2011」计算器tj
    你被要求设计一个计算器完成以下三项任务:1.给定y、z、P,计算yzmodP的值2.给定y、z、P,计算满足xy≡z(modP)的最小非负整数x;3.给定y、z、P,计算满足yx≡z(modP)的最小非负整数x。输入第一行包含两个正整数T,K分别表示数据组数和询问类型-对于一个测试点内的所有数据,询问类......
  • P2484 [SDOI2011] 打地鼠
    题目描述2020.4.29数据更新。打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中。玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高。游戏中的锤子每次只能打一只地鼠,如果多只地鼠同时探出头,玩家只能通过多次挥舞......
  • BZOJ 2243: [SDOI2011]染色 树链剖分+线段树
    2243:[SDOI2011]染色TimeLimit: 20Sec  MemoryLimit: 512MBSubmit: 7623  Solved: 2855[Submit][Status][Discuss]Description给定一棵有n个节点的无根树和m个操作,操作有2类:1、将节点a到节点b路径上所有点都染成颜色c;2、询问节点a到节点b路径上的颜色段数量(连续......
  • NC20573 [SDOI2011]染色
    题目链接题目题目描述给定一棵有n个节点的无根树和m个操作,操作有2类:1、将节点a到节点b路径上所有点都染成颜色c;2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。请你写一个程序依次完成这m......