【SDOI2011】消耗战

题目

题目描述
在一场战争中,战场由 nn 个岛屿和 n-1n−1 个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为 11 的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他 kk 个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。

侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到 11 号岛屿上)。不过侦查部门还发现了这台机器只能够使用 mm 次,所以我们只需要把每次任务完成即可。

输入格式
第一行一个整数 nn,表示岛屿数量。

接下来 n-1n−1 行,每行三个整数 u,v,wu,v,w ,表示 uu 号岛屿和 vv 号岛屿由一条代价为 ww 的桥梁直接相连。

第 n+1n+1 行,一个整数 mm ,代表敌方机器能使用的次数。

接下来 mm 行,第 ii 行一个整数 k_ik
i

,代表第 ii 次后,有 k_ik
i

个岛屿资源丰富。接下来 k_ik
i

个整数 h_1,h_2,…, h_{k_i}h
1

,h
2

,…,h
k
i


,表示资源丰富岛屿的编号。

输出格式
输出共 mm 行,表示每次任务的最小代价。

输入输出样例
输入 #1 复制
10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6
输出 #1 复制
12
32
22
说明/提示
数据规模与约定
对于 10%10% 的数据,n\leq 10, m\leq 5n≤10,m≤5 。
对于 20%20% 的数据,n\leq 100, m\leq 100, 1\leq k_i\leq 10n≤100,m≤100,1≤k
i

≤10 。
对于 40%40% 的数据,n\leq 1000, 1\leq k_i\leq 15n≤1000,1≤k
i

≤15 。
对于 100%100% 的数据,2\leq n \leq 2.5\times 10^5, 1\leq m\leq 5\times 10^5, \sum k_i \leq 5\times 10^5, 1\leq k_i< n, h_i\neq 1, 1\leq u,v\leq n, 1\leq w\leq 10^52≤n≤2.5×10
5
,1≤m≤5×10
5
,∑k
i

≤5×10
5
,1≤k
i

<n,h
i



=1,1≤u,v≤n,1≤w≤10
5

思路

先看看对单个询问怎么做。

有个很巧妙的做法,预处理出根到每个点的路径上的min边权w[i]。

dp[i]表示以i为根的答案,只有两种情况:1 w[i] 2 dp[儿子]的和

(当然,还要考虑i是不是选中点等,自己思考吧)

这题询问很多,显然每次询问我们必须用跟k相关的时间解决,而不能跟n相关。

所以我们要建一个虚树,只包含那k个点和他们的lca。直接在这棵树上dp即可。

怎么建虚树?

首先把点按dfs序排序,从小到大插入。

之后一直用栈维护最后的一条树链,每加入一个点now,求出now和最后一个点pre的lca。

如果lca就是pre,那么将now入栈,退出。

否则lca就是pre的爸爸,那么如果lca是倒数第二个点fx的儿子,将pre出栈,lca和now入栈,退出。

否则将pre出栈,lca作为pre递归处理。

(在此过程中要建边,自己思考)

(此代码在bzoj上拿了rank1,并超过rank2 500ms)

upd:今天突然想到,如果离线读进来每组询问的点,由于dfn序的值域是1-n的,我们可以把点按dfs序先插到值域的数组里,记录所属询问编号。

之后再遍历一遍把他们插回询问里。这样排序时间就从O(NlogN)变成O(N)了。

我们也可以离线用tarjan将求lca的时间优化到O(N)。

那么我们就可以O(n)建虚树了。这题也就可以O(N)了。

代码

#include<bits/stdc++.h>
using namespace std;
void dfs1(int u)
{
	siz[u]=1; dep[u]=dep[fa]+1;
	for(int i=ls[u]; i; i=e[i].next)
	{
		int v=e[i].to; if(v==fa[u]) continue;
		mi[v]=min(mi[u],e[i].w); fa[v]=u;
		dfs1(v,u),siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
}
void dfs2(int u,int tp)
{
	top[u]=tp; dfn[u]=++tot;
	if(!son[u]) return;
	dfs2(son[u],tp);
	for(int i=ls[u]; i; i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];else y=fa[top[y]];
	}
	return dep[x]<dep[y]?x:y;
}
void ins(int x)
{
	if(tp==1)
	{
		st[++tp]=x; return;
	}
	int LCA=lca(x,st[tp]);
	if(LCA==st[tp]) return;
	while(tp>1&&dfn[st[tp-1]]>=dfn[l]) v[st[tp-1]].push_back(st[tp]),--tp;
	if(st[tp]!=l) v[l].push_back(st[tp]),st[tp]=l;
	st[++tp]=x;
}
int main()
{
	scanf("%d",&n);
	for(int i=1,x,y,z; i<=n; i++) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
	mi[1]=inf;
	dfs1(1,0); dfs2(1,1);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int m; scanf("%d",&m);
		for(int i = 1; i <= m; ++ i) is[i] = read();
		sort(is + 1,is + m + 1,cmp);
		s[t = 1] = 1;
		for(re int i = 1; i <= m; ++ i) push(is[i]);
		while(t > 0) v[s[t - 1]].push_back(s[t]),--t;
		printf("%lld\n",dp(1));
	}
}
















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值