2286: [Sdoi2011消耗戰
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1815 Solved: 645
[Submit][Status][Discuss]
Description
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
Sample Input
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
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;
}