挂个求调慢慢等
查看原帖
挂个求调慢慢等
315655
帝都_henry26268楼主2024/7/24 19:30

Rt,写+调花了一个上午,现在50pts,内有注释 是利用单调栈实现的

史山警告:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector> 
#include<cstring>
#include<stack>

#define ll long long
using namespace std;

const int MAXN = 1e6+10;//注意虚树是要开两倍的点空间的 
const int MAXM = 1e6+10;
const int LGN = 25+5;
const ll INF = 0x3f3f3f3f3f;

int cnt = 0;//边的数量(链式前向星) 
int dfncnt = 0;//dfn点序 

//闲话:多测只能在dfs中清空好烦好难调 

struct Edge{
	int v,w;
};

//虚数的边 
struct Edge_Vt{
	int nxt,v,w;	
}edge[MAXN<<1]; 

//链式前向星加边 
int head[MAXN<<1];
void add(int x,int y,ll val){
	edge[++cnt].nxt = head[x];
	edge[cnt].v = y,edge[cnt].w = val;
	head[x] = cnt;
}

vector<Edge> vct[MAXN<<1];//实树的边
 

int n,q;
int dfn[MAXN],dep[MAXN];//每个点的dfn和dep 
int fa[MAXN][LGN],mxk[MAXN];//倍增lca,u节点的k级祖先是什么,以及u的最大的祖先是多少级 
ll disfa[MAXN][LGN];//表示到达k级祖先的边权最小值(因为题目中我们只需知道切断这当中的最小边权就可以切断联系) 

int Tc; 

ll dp[MAXN];//表示树型dp的方案数量 

int lst[MAXN];//关键点的列表 
bool qry[MAXN];//表示这个点是否是关键点 

stack<int> st;//虚树上维护的单调栈
//虚数维护的栈之所以是单调栈,是因为这个栈中的点满足栈顶的dfn是小于栈底的dfn序的,并且其任意时刻都是维护虚树上的一条链的 

void dfs(int u,int par){//dfs预处理 
	
	if(u != 1)dep[u] = dep[par]+1;
	else dep[u] = 1;
	dfn[u] = ++dfncnt;
	
	
	fa[u][0] = par;mxk[u] = 0;
	for(int i = 1; fa[fa[u][i-1]][i-1] ;i++) fa[u][i] = fa[fa[u][i-1]][i-1],disfa[u][i] = min(disfa[u][i-1],disfa[fa[u][i-1]][i-1]),mxk[u] = i;
	
	for(int i = 0;i < (int)vct[u].size();i++){
		int v = vct[u][i].v,w = vct[u][i].w;
		if(v == par)continue;
		disfa[v][0] = w;
		dfs(v,u);
	}
	return ;
}

ll d;//这个d是额外的更新距离的边权,d是我们到lca的边权 
int lca(int a,int b){//倍增lca的标准形式 
	d = (1ll<<35);	
	if(dep[b] > dep[a])swap(a,b);
	for(int i = mxk[a];i >= 0;i--) if(dep[fa[a][i]] >= dep[b]) d = min(d,disfa[a][i]),a = fa[a][i];
	if(a == b)return a;
	for(int i = mxk[a];i >= 0;i--) if(fa[a][i] != fa[b][i]) d = min(d,min(disfa[a][i],disfa[b][i])),a = fa[a][i],b = fa[b][i];
	d = min(d,min(disfa[a][0],disfa[b][0]));return fa[a][0];
} 

int sec_top(){//返回单调栈的次大值 (次栈顶) 
	int p = st.top(),sc_top; st.pop();
	if(st.empty())sc_top = 0;//正常来说不会进到这里,可以证明有1的情况不可能有dfn比1还小的点 
	else sc_top = st.top();
	st.push(p);return sc_top;
}
bool cmp(int a,int b) {return dfn[a] < dfn[b]; }//虚数建立前要先满足dfn序的顺序排列 

void build_Vt(int pt){//单调栈建立虚树(本质上是建立所有关键点与其Lca的虚拟树,本质是我们只关心关键点间的祖先关系和权值)
	while(!st.empty())st.pop();//还有就是这个栈每次会留下一个1,要把1也pop掉 
	sort(lst+1,lst+1+pt,cmp);//按照dfn排序维护正确性 
	st.push(1);//初始钦定根节点为关键点(大概率是有这个点的除非是所有关键点在一条链上,此时不管他就好,不影响答案)
	head[1] = 0;cnt = 0;//一开始的时候要消除上一次的边 
	for(int i = 1;i <= pt;i++){//按照dfn序列以此加入关键点
		int u = lst[i],Lca = lca(u,st.top());//这个Lca是永远落在原来维护的链与新加入点所在链的公共边上的(正确性有dfn序保证) 
		//if(u == 1)continue;
		if(Lca != st.top()){
			//进到这里面说明了,栈顶一定不存在与新的LCA的链上,那么原来链上的全部pop掉(注意这里是对原来的链连边的) 
			while(dfn[Lca] < dfn[sec_top()]){lca(sec_top(),st.top()),add(sec_top(),st.top(),d),st.pop();}//我们对次栈顶做决策(次栈顶在链上为止) 
			if(dfn[Lca] > dfn[sec_top()]){head[Lca] = 0,lca(Lca,st.top()),add(Lca,st.top(),d),st.pop(),st.push(Lca);}//说明Lca是位于原来的栈顶与次栈顶之间 
			else {lca(Lca,st.top()),add(Lca,st.top(),d),st.pop();}//这种情况下Lca就是次栈顶(dfn既不比次栈顶大又不比次栈顶小那当然是次栈顶啦) 
		}
		head[u] = 0;//清空图(这是u的第1次进栈,此时应当消除之前的影响) 
		st.push(u);
	}while(st.size() > 1){
		//cout << st.top() << " " << sec_top() << endl;
		lca(sec_top(),st.top());//注意因为这里连的都是单项边,所以我们都是要注意u,v的区分性的 
		add(sec_top(),st.top(),d);
		st.pop();//注意最后一条链自己手动连 
	} return ;
}

void trdp(int u){
	
	dp[u] = 0;//同样要清空多测带来的影响 
	for(int i = head[u];i;i = edge[i].nxt){
		int v = edge[i].v;ll w = edge[i].w;
		trdp(v);
		if(qry[v])qry[v] = 0,dp[u] = dp[u] + w;//注意这里的时候将这些处理过的也标记0 
		else dp[u] = min(dp[v],1ll*w);
	}
} 

int main(){
	
	scanf("%d",&n);
	for(int i = 1;i < n;i++){
		int u,v;ll w;scanf("%d%d%lld",&u,&v,&w);
		vct[u].push_back((Edge){v,w});
		vct[v].push_back((Edge){u,w});
	} 
	
	for(int i = 1;i <= n;i++)dp[i] = INF; 
	
	dfs(1,0); 
	
	scanf("%d",&q);
	while(q--){
		int pt;scanf("%d",&pt);
		for(int i = 1;i <= pt;i++){
			scanf("%d",&lst[i]);
			qry[lst[i]] = true;
		}
		
		build_Vt(pt); 
		trdp(1);
		printf("%lld\n", dp[1] );
	}
	
	return 0;
} 
2024/7/24 19:30
加载中...