[bzoj2286][Sdoi2011]消耗战——虚树入门例题 dalaos' blogs Some Links

版权声明:欢迎大家转载,转载请标明出处。 https://blog.csdn.net/ylsoi/article/details/83095505

题目大意:

在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。

思路:

如果只考虑一次询问,是可以在 O ( n ) O(n) 的时间处理出来的。
设dp[u]为结点u以及其子树全部都符合题目要求的最小代价,如果u本身自己是关键点,那么dp[u]=w[u] (w[u]表示u和父亲之间的连边),否则 d p [ u ] = min ( d p [ s o n u ] , w [ u ] ) dp[u]=\min(\sum dp[son_u],w[u])
询问组数太多,但是询问的总点数规模不大,如果我们可以把每一次的dp优化成只在关键点以及和关键点有联系的点上转移,那么总的复杂度也必定是线性的。
但是上面的转移显然要涉及所有的点,不如我们在树上把关键点给标红(想象一下),考虑题目本身的性质,dfs序相邻的关键点的lca应该也要标红,那么不难发现,任意相邻的两个红点之间,至多有一条边被删除。
这时只在红点之间的转移就和之前一样了,但是相邻两个红点之间的边权最小值也并不好处理,于是我们将dp方程的w[u]改成它到根的路径上的边权的最小值(这样也是对的),每一次的转移就可以变得很方便了。
于是上面所说的只有红点组成的“树”就是所谓的虚树了。考虑如何将虚树建出,可以将关键点按照dfs序排序之后相邻点之间求lca,也可以将关键点排序之后放到栈中维护此时的dfs栈,遇到另外一个分支的时候将lca求出,将栈中的元素弹出直到lca为之,然后将lca也压入入栈中,dp的过程在这里进行即可。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<endl
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj2286.in","r",stdin);
	freopen("bzoj2286.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=25e4+10;
const ll inf=1e18;
int n,m,in[maxn],cnt_dfn,dep[maxn],st[maxn][21],Log[maxn];
int beg[maxn],to[maxn<<1],las[maxn<<1],cnte=1;
int qu[maxn],sz;
ll w[maxn<<1],Min[maxn],dp[maxn];

void add(int u,int v,ll val){
	las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v; w[cnte]=val;
	las[++cnte]=beg[v]; beg[v]=cnte; to[cnte]=u; w[cnte]=val;
}

void dfs(int u,int f){
	in[u]=++cnt_dfn;
	dep[u]=dep[f]+1; st[u][0]=f;
	for(int i=beg[u];i;i=las[i]){
		int v=to[i];
		if(v==f)continue;
		Min[v]=min(Min[u],w[i]);
		dfs(v,u);
	}
}

int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	while(dep[x]!=dep[y]){
		int d=Log[dep[x]-dep[y]];
		x=st[x][d];
	}
	if(x==y)return x;
	for(int d=18;d>=0;--d)
		if(st[x][d] && st[y][d] && st[x][d]!=st[y][d])
			x=st[x][d],y=st[y][d];
	return st[x][0];
}

void init(){
	read(n);
	REP(i,2,n)Log[i]=Log[i/2]+1;
	int u,v; ll val;
	REP(i,1,n-1)read(u),read(v),read(val),add(u,v,val);
	Min[1]=inf; dfs(1,0);
	REP(j,1,18)REP(i,1,n)if((1<<j)<=dep[i]-1)
		st[i][j]=st[st[i][j-1]][j-1];
}

bool cmp(int x,int y){
	return in[x]<in[y];
}

int usd[maxn],s[maxn],tp,mark[maxn];

void solve(int id){
	s[++tp]=1;
	REP(i,1,sz){
		int u,f,a=lca(s[tp],qu[i]);
		while(dep[(u=s[tp])]>dep[a]){
			--tp;
			if(mark[u]==id)dp[u]=Min[u];
			else dp[u]=min(dp[u],Min[u]);
			if(dep[a]>dep[s[tp]])s[++tp]=a;
			f=s[tp];
			if(usd[f]==id)dp[f]+=dp[u];
			else usd[f]=id,dp[f]=dp[u];
		}
		s[++tp]=qu[i];
	}
	while(tp){
		int u=s[tp];
		if(mark[u]==id)dp[u]=Min[u];
		else dp[u]=min(dp[u],Min[u]);
		if(tp>=2){
			if(usd[s[tp-1]]==id)dp[s[tp-1]]+=dp[u];
			else dp[s[tp-1]]=dp[u];
		}
		--tp;
	}
	printf("%lld\n",dp[1]);
}

void work(){
	read(m);
	REP(ca,1,m){
		read(sz);
		REP(i,1,sz)read(qu[i]),mark[qu[i]]=ca;
		sort(qu+1,qu+sz+1,cmp);
		solve(ca);
	}
}

int main(){
	File();
	init();
	work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ylsoi/article/details/83095505
今日推荐