bzoj-2286 消耗戰【虛樹+倍增lca+單調棧】

2286: [Sdoi2011消耗戰

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 1815  Solved: 645
[Submit][Status][Discuss]

Description

在一場戰爭中,戰場由n個島嶼和n-1個橋樑組成,保證每兩個島嶼間有且僅有一條路徑可達。現在,我軍已經偵查到敵軍的總部在編號爲1的島嶼,而且他們已經沒有足夠多的能源維繫戰鬥,我軍勝利在望。已知在其他k個島嶼上有豐富能源,爲了防止敵軍獲取能源,我軍的任務是炸燬一些橋樑,使得敵軍不能到達任何能源豐富的島嶼。由於不同橋樑的材質和結構不同,所以炸燬不同的橋樑有不同的代價,我軍希望在滿足目標的同時使得總代價最小。
偵查部門還發現,敵軍有一臺神祕機器。即使我軍切斷所有能源之後,他們也可以用那臺機器。機器產生的效果不僅僅會修復所有我軍炸燬的橋樑,而且會重新隨機資源分佈(但可以保證的是,資源不會分佈到1號島嶼上)。不過偵查部門還發現了這臺機器只能夠使用m次,所以我們只需要把每次任務完成即可。

Input

第一行一個整數n,代表島嶼數量。

接下來n-1行,每行三個整數u,v,w,代表u號島嶼和v號島嶼由一條代價爲c的橋樑直接相連,保證1<=u,v<=n且1<=c<=100000。

第n+1行,一個整數m,代表敵方機器能使用的次數。

接下來m行,每行一個整數ki,代表第i次後,有ki個島嶼資源豐富,接下來k個整數h1,h2,…hk,表示資源豐富島嶼的編號。

Output

輸出有m行,分別代表每次任務的最小代價。

 

Sample Input

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

Sample Output

12
32
22

HINT

 對於100%的數據,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1

對於每次查詢,如果用一次樹型dp就能得出結果。

dp方程:

f[father]+=fmin(g[son]?inf:f[son],(min_e(son,father));(g[i]標記是否是關鍵點)

這個時間效率很直觀O(m*n)

每次查詢,我們只需要遍歷關鍵點與關鍵點之間的lca,其它點時可忽略的或可跳躍的。

那麼就需要用到虛樹的技巧了,虛樹就是通過維護一個單調棧把樹的關鍵點和它們之間的lca按照dfs序遍歷一遍,遍歷的過程中通過單調棧的調整來理清樹的父親和兒子之間的關係。

首先,對樹節點進行dfs。在期間對節點進行標號dfn。

然後,維護一個單調棧。這個單調棧的節點都在一條鏈上。

對於棧頂元素 p,棧次頂元素 q, 即將插入節點x 有如下關係:

1.lca是p.此時dfn(x)>dfn(p)=dfn(lca)


這說明 x在p的下面,直接把x入棧即可

2.p和x分立在lca的兩棵子樹下.此時 dfn(x)>dfn(p)>dfn(ilca)

這時候就有三種討論了

針對這道題的連邊就是樹型dp處理

(1)如果dfn(q)>dfn(lca),可以直接連邊q->p,然後退一次棧.


(2)如果dfn(q)=dfn(lca),說明q=lca,直接連邊lca->p,把p退棧,此時子樹已經構建完畢.

(3)如果dfn(q)<dfn(lca),說明lca被p與q夾在中間,此時連邊lca->q,把p退棧,再把lca壓入棧.此時子樹構建完畢.


重複判斷上述過程

這裏處理  min_e(p,q) p到q的路徑中權值最小的邊。需要用倍增lca或者樹剖也是可以的。這個參見《挑戰程序設計競賽》吧,改改代碼就可以了

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
#define find_min(a,b) a>b?b:a
#define MAX_V 250008
#define MAX_LOG_V 21
#define INF 0x3f3f3f3f
#define inf (1ll<<40)  
using namespace std;
typedef long long int ll; 
struct edge{
	int to,cost;
};
vector<edge> G[MAX_V];
int par[MAX_LOG_V][MAX_V];//v節點向上走2^k步走到的節點 
int mng[MAX_LOG_V][MAX_V];//v節點向上走2^k步中路過最小的邊 
int dfn[MAX_V];//每個點的dfs序標號 
int dep[MAX_V];//深度 
ll f[MAX_V];//樹型dp 
struct node{
	int h,dfn;
}hs[MAX_V];
bool g[MAX_V];//是否是關鍵點 
int sta[MAX_V*2];// 模擬棧 
int icnt;//棧頂 、id 
int swap(int &x,int &y)
{//交換 
	x=x^y;y=x^y,x=x^y;
}
//當前點、父親節點 、深度、連接父親點的邊權 
void dfs(int v,int p,int d,int pre_e)
{//lca搜索預處理 
	par[0][v]=p,dep[v]=d,mng[0][v]=pre_e,dfn[v]=icnt++;
	for(int i=0;i<G[v].size();++i)
		if(G[v][i].to!=p) 
			dfs(G[v][i].to,v,d+1,G[v][i].cost);
}
void init_tree()
{//預處理lca查詢 
	icnt=0;//id初始化爲0 
	dfs(1,-1,0,INF); 
	for(int k=0;k+1<MAX_LOG_V;++k)
		for(int v=1;v<=MAX_V;++v)
		{
			if(par[k][v]<0)//超過樹根 
				par[k+1][v]=-1,mng[k+1][v]=INF;
			else
			{//能前進 2^(k+1)步 
				int u=par[k][v]; 
				par[k+1][v]=par[k][u]; 
				mng[k+1][v]=find_min(mng[k][v],mng[k][u]);
			}
		}
}
int lca(int u,int v)
{
	if(dep[u]>dep[v]) swap(u,v);
	//先到同一深度 
	for(int k=0;k<MAX_LOG_V;++k)
		if((dep[v]-dep[u])>>k & 1)
			v=par[k][v];
	if(u==v) return u;
	//同時向上 二分查詢 
	for(int k=MAX_LOG_V-1;k>=0;--k)
		if(par[k][u]!=par[k][v])
			u=par[k][u],v=par[k][v];
	return par[0][u];
}
int min_e(int u,int v)
{
	int ilca=lca(u,v);
	int res=INF;
	//u->lca 
	int mov;
	if(dep[ilca]<dep[u])
	{
		mov=dep[u]-dep[ilca];
		for(int k=0;k<MAX_LOG_V;++k)
			if(mov>>k &1)
				res=find_min(res,mng[k][u]),u=par[k][u];
	}
	//v->lca 
	if(dep[ilca]<dep[v])
	{
		mov=dep[v]-dep[ilca];
		for(int k=0;k<MAX_LOG_V;++k)
			if(mov>>k &1)
				res=find_min(res,mng[k][v]),v=par[k][v];
	}
	return res;
}
void add_edge(int u,int v,int c)
{ 
	G[u].push_back((edge){v,c}); 
	G[v].push_back((edge){u,c});
}
void init()
{//初始化邊數置0 
	for(int i=0;i<MAX_V;++i)
		G[i].clear();
}
int cmp(const void *a,const void *b)
{
	return ((node *)a)->dfn-((node *)b)->dfn;
}
ll fmin(ll a,ll b)
{
	return a>b?b:a;
}
void solve(int k)
{
	for(int i=1;i<=k;++i)
	{
		int o=hs[i].h;
		hs[i].dfn=dfn[o];//同步搜索序id
	}
	qsort(hs+1,k,sizeof(hs[0]),cmp);
	int tp=0;
	sta[tp]=0;
	sta[++tp]=1;
	f[1]=0,g[1]=0;
	for(int i=1;i<=k;++i)
	{
		int p=sta[tp],q=sta[tp-1],x=hs[i].h;
		int ilca=lca(p,x);
		while(dfn[p]>dfn[ilca])
		{
			if(dfn[q]<=dfn[ilca])
			{
				int tmp=fmin(g[tp]?inf:f[tp],(ll)min_e(p,ilca));
				sta[tp--]=0;
				if(ilca!=q)
					sta[++tp]=ilca,f[tp]=0,g[tp]=0;
				f[tp]+=tmp;
				break;
			}
			else
			{
				f[tp-1]+=fmin(g[tp]?inf:f[tp],(ll)min_e(p,q));
				sta[tp--]=0;
			
			}
			p=sta[tp],q=sta[tp-1];
		}
		if(sta[tp]!=x)
			sta[++tp]=x,f[tp]=0;
		g[tp]=1;
	}
	while(tp>1)
	{
		int p=sta[tp],q=sta[tp-1];
		f[tp-1]+=fmin(g[tp]?inf:f[tp],(ll)min_e(p,q));
		sta[tp--]=0;
		
	}
	printf("%lld\n",f[tp--]);
}
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		init();
		int u,v,w;
		for(int i=0;i<n-1;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			add_edge(u,v,w);
		}
		init_tree();
		int m,k,h;
		scanf("%d",&m);
		while(m--)
		{
			scanf("%d",&k);
			for(int i=1;i<=k;++i)
				scanf("%d",&hs[i].h);
			solve(k);
		}
	}
	return 0;
}



發佈了94 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章