P2495 [SDOI2011] 消耗战

题目描述

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

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

输入格式

第一行一个整数 nnn,表示岛屿数量。

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

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

接下来 mmm 行,第 iii 行一个整数 kik_iki ,代表第 iii 次后,有 kik_iki 个岛屿资源丰富。接下来 kik_iki 个整数 h1,h2,...,hkih_1,h_2,..., h_{k_i}h1,h2,...,hki ,表示资源丰富岛屿的编号。

输出格式

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

输入输出样例 #1

输入 #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\%10% 的数据,n≤10,m≤5n\leq 10, m\leq 5n10,m5
  • 对于 20%20\%20% 的数据,n≤100,m≤100,1≤ki≤10n\leq 100, m\leq 100, 1\leq k_i\leq 10n100,m100,1ki10
  • 对于 40%40\%40% 的数据,n≤1000,1≤ki≤15n\leq 1000, 1\leq k_i\leq 15n1000,1ki15
  • 对于 100%100\%100% 的数据,2≤n≤2.5×105,1≤m≤5×105,∑ki≤5×105,1≤ki2n2.5×105,1m5×105,ki5×105,1ki<n,hi=1,1u,vn,1w105

算法思路

  • 采用虚树优化+树链剖分+树形DP解决:
  • 树链剖分预处理:
  • 第一次 DFS 计算子树大小、深度、父节点和 DFS 序。
  • 第二次 DFS 进行重链剖分,建立重链序列。
  • 线段树维护路径最小值:
  • 用线段树维护重链序列上节点到父节点边权的最小值。
  • 支持 O(log⁡n)O(\log n)O(logn) 查询任意两点路径上的最小边权。

虚树构建:

  • 对关键点按 DFS 序排序。
  • 用栈动态维护右链,通过求 LCA 压缩树结构。
  • 虚树边权为原树路径最小边权。
  • 树形 DP:
  • 定义 dp[u]dp[u]dp[u] 表示使 uuu 的子树中所有关键点与 uuu 断开的最小代价。

状态转移:

  • 若子节点 vvv 是关键点:dp[u]+=w(u,v)dp[u] += w(u,v)dp[u]+=w(u,v)(必须切断该边)。
  • 否则:dp[u]+=min⁡(w(u,v),dp[v])dp[u] += \min(w(u,v), dp[v])dp[u]+=min(w(u,v),dp[v])(可选切断边或处理子树)。

复杂度分析

  • 预处理:树链剖分与线段树构建 O(nlog⁡n)O(n \log n)O(nlogn)
  • 单次查询:
  • 虚树构建 O(klog⁡k)O(k \log k)O(klogk)(排序)+ O(klog⁡n)O(k \log n)O(klogn)(求 LCA 和边权)。
  • 树形 DP O(k)O(k)O(k)

总复杂度:O(nlog⁡n+q⋅k(log⁡k+log⁡n))O(n \log n + q \cdot k (\log k + \log n))O(nlogn+qk(logk+logn))

  • 正确性证明
  • 虚树性质:保留关键点和 LCA 后,任意两点间路径信息由虚树边权精确表示。
  • DP 最优子结构:每个子树的决策独立且覆盖所有切断方案:
  • 关键点必须切断与父节点的连接。
  • 非关键点可切断当前边或递归处理子树,取最小值。
  • 路径最小值正确性:树链剖分将路径分解为 O(log⁡n)O(\log n)O(logn) 条重链,线段树保证最小值查询正确。

详细代码

#include
#define int long long
using namespace std;
const int N=1e6+5;
int h[N],h1[N],to[N],to1[N],w[N],w1[N],ne[N],ne1[N],tot,tot1;
int fa[N],dfn[N],top[N],son[N],s[N],cnt,de[N],sta[N];
int num[N],zhi[N],shu[N],sum[N*2],n,q,ru[N],minnd,ans[N];
bool vis[N];
set<int>s1;
bool cmp(int aa,int bb)
{
	return dfn[aa]<dfn[bb];
}
void add(int a,int b,int c)
{
	tot++;
	ne[tot]=h[a];
	h[a]=tot;
	to[tot]=b;
	w[tot]=c;
}
void add1(int a,int b,int c)
{
	tot1++;
	ne1[tot1]=h1[a];
	h1[a]=tot1;
	to1[tot1]=b;
	w1[tot1]=c;
}
void dfs1(int u,int f)
{
	s[u]=1;
	de[u]=de[f]+1;
	dfn[u]=++cnt;
	fa[u]=f;
	for(int i=h[u];i;i=ne[i])
	{
		int j=to[i];
		if(j!=f)
		{
			num[j]=w[i];
			dfs1(j,u);
			s[u]+=s[j];
			if(s[son[u]]<s[j])
				son[u]=j;
		}
	}
}
void dfs2(int u)
{
	if(son[u])
	{
		top[son[u]]=top[u];
		zhi[son[u]]=++zhi[0];
		shu[zhi[0]]=son[u];
		dfs2(son[u]);
	}
	for(int i=h[u];i;i=ne[i])
	{
		int j=to[i];
		if(!top[j])
		{
			top[j]=j;
			zhi[j]=++zhi[0];
			shu[zhi[0]]=j;
			dfs2(j);
		}
	}
}
int qiu(int k,int l,int r,int nowl,int nowr)
{
	if(nowr<l||nowl>r)return 1e12;
	if(nowl>=l&&nowr<=r)
		return sum[k];
	int mid=nowl+nowr>>1;
	int minn=1e12;
	if(mid>=nowl)minn=min(minn,qiu(k<<1,l,r,nowl,mid));
	if(mid<nowr)minn=min(minn,qiu((k<<1)+1,l,r,mid+1,nowr));
	return minn;
}
void build(int k,int l,int r)
{
	int mid=l+r>>1;
	if(l==r)
	{
		sum[k]=num[shu[l]];
		return;
	}
	build(k<<1,l,mid);
	build((k<<1)+1,mid+1,r);
	sum[k]=min(sum[k<<1],sum[(k<<1)+1]);
}
int find(int x,int y)
{
	int fx=top[x];
	int fy=top[y];
	minnd=1e12;
	while(fx!=fy)
	{
		if(de[fx]<de[fy])
			swap(fx,fy),swap(x,y);
		minnd=min(minnd,qiu(1,zhi[fx],zhi[x],1,zhi[0]));
		x=fa[fx];fx=top[x];
	}
	if(de[x]>de[y])swap(x,y);
	minnd=min(minnd,qiu(1,zhi[x]+1,zhi[y],1,zhi[0]));
	return x;
}
void dp(int u)
{
	ans[u]=0;
	for(int i=h1[u];i;i=ne1[i])
	{
		int j=to1[i];
		dp(j);
		if(s1.count(j))ans[u]+=w1[i];
		else
			ans[u]+=min(ans[j],w1[i]);
	}
}
signed main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	int a,b,c;
	for(int i=1;i<n;i++)
	{
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
//	memset(sum,1e12,sizeof(sum));
	dfs1(1,0);
	zhi[0]=zhi[1]=shu[1]=top[1]=1;
	dfs2(1);
	build(1,1,zhi[0]);
	cin>>q;
	while(q--)
	{
		s1.clear();
		tot1=0;
		int kk;
		cin>>kk;
		for(int i=1;i<=kk;i++)
		{
			cin>>ru[i];
			s1.insert(ru[i]);
		}
		sort(ru+1,ru+1+kk,cmp);
		int be=1;
		sta[1]=1;
		h1[1]=0;
		for(int i=1;i<=kk;i++)
		{
			int lc=find(sta[be],ru[i]);
			if(lc!=sta[be])
			{
				while(dfn[lc]<dfn[sta[be-1]])
				{
					find(sta[be],sta[be-1]);
					add1(sta[be-1],sta[be],minnd);
					be--;
				}
				if(dfn[sta[be-1]]<dfn[lc])
				{
					find(sta[be],lc);
					h1[lc]=0;
					add1(lc,sta[be],minnd);
//					cout<
					sta[be]=lc;
				}
				else
				{
					find(sta[be],lc);
					add1(lc,sta[be],minnd);
//					cout<
					be--;
				}
			}
			h1[ru[i]]=0;
			sta[++be]=ru[i];
//			cout<
		}
		while(be>1)
		{
			find(sta[be-1],sta[be]);
			add1(sta[be-1],sta[be],minnd);
			be--;
		}
//		memset(,0,sizeof(dp));
//		for(int i=h1[1];i;i=ne1[i])
//			cout<<1<<" "<
//		cout<<1<<" "<
//		cout<<24<
		dp(1);
		cout<<ans[1]<<'\n';
	}
//	for(int i=1;i<=n*2;i++)
//		cout<
	return 0;
}

你可能感兴趣的:(算法,图论,数据结构,c++,虚数,动态dp)