码迷,mamicode.com
首页 > 其他好文 > 详细

UOJ87 mx的仙人掌 虚仙人掌

时间:2021-02-06 12:15:50      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:stp   去重   比较   else   题目   ==   pac   递归调用   reverse   

UOJ87 mx的仙人掌

这里没有用传统的方点外接圆点的做法,而是方点虚树上儿子跳到方点所在环上单调队列处理,本质上是一样的.

code

//爽! 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=900900;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<‘0‘||ch>‘9‘){
		if(ch==‘-‘)f=-1;
		ch=getchar();
	}
	while(‘0‘<=ch&&ch<=‘9‘)x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f; 
}
int n,m;
#define pr pair<int,ll>
#define mk make_pair
#define fi first
#define se second
map<int,int>e[N];
vector<pr>mp[N];
void add(int u,int v,int w){//重边(二元环单独处理) 
	if(e[u].count(v))e[u][v]=min(e[u][v],w);
	else e[u][v]=w;
	if(e[v].count(u))e[v][u]=min(e[v][u],w);
	else e[v][u]=w;
}
vector<int>edge[N];
int tot,dfn[N],low[N],Index,fa[N];
ll dis[N],sum[N];
void solve(int x,int rt,int Edge){
	ll len=dis[x]-dis[rt]+Edge,tmp;
	++tot;
	sum[tot]=len;
	mp[rt].push_back(mk(tot,0));
	mp[tot].push_back(mk(rt,0));
	for(int i=x;i!=rt;i=fa[i]){
		tmp=min(dis[i]-dis[rt],len-dis[i]+dis[rt]);
		mp[tot].push_back(mk(i,tmp));
		mp[i].push_back(mk(tot,tmp));
	}
}
void tarjan(int u){
	dfn[u]=low[u]=++Index;
	int v;
	for(map<int,int> :: iterator it=e[u].begin();it!=e[u].end();++it){//递归调用,不然迭代器会出错  
		v=it->fi;
		if(v==fa[u])continue;
		if(!dfn[v]){
			dis[v]=dis[u]+it->se;
			fa[v]=u;
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(dfn[u]<low[v]){
				mp[u].push_back(mk(v,it->se));
				mp[v].push_back(mk(u,it->se));
			}
			//我现行的圆方树建法会忽略二环 
		}
		else low[u]=min(low[u],dfn[v]);
	}
	for(map<int,int> :: iterator it=e[u].begin();it!=e[u].end();++it){
		v=it->fi;
		if(fa[v]!=u&&dfn[v]>=dfn[u])solve(v,u,it->se);
	}
}
int f[N][20],dep[N],l[N],num;
ll Len[N];
void dfs(int u,int fa){
	f[u][0]=fa,dep[u]=dep[fa]+1;
	l[u]=++num;
//	printf("fa=%d son=%d\n",fa,u);
//	printf("tree %d %lld\n",u,Len[u]);
	for(int v,i=0;i<mp[u].size();++i){
		ll w;
		v=mp[u][i].fi;
		if(v==fa)continue;
		w=mp[u][i].se;
		Len[v]=Len[u]+w;
		dfs(v,u);
	}
}
void prework(){
	for(int i=1;i<=19;++i){
		for(int j=1;j<=tot;++j){
			f[j][i]=f[f[j][i-1]][i-1];
		}
	}
}
inline int getlca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=19;i>=0;--i){
		if(dep[f[x][i]]>=dep[y])x=f[x][i];
		if(dep[x]==dep[y])break;
	}
	if(x==y)return x;
	for(int i=19;i>=0;--i)
		if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
int a[N],s[N],top;
bool cmp(int aa,int bb){
	return l[aa]<l[bb];
}
void build(int x){
	if(top==0){
		s[++top]=x;
		return;
	}
	int lca=getlca(s[top],x);
	while(top>1&&dep[lca]<dep[s[top-1]]){
		edge[s[top-1]].push_back(s[top]);
		--top;
	}
	while(top>0&&dep[lca]<dep[s[top]]){
		edge[lca].push_back(s[top]);
		--top;
	}
	if(top==0||s[top]!=lca)s[++top]=lca;
	s[++top]=x;
}
//这里比较刚,没有用传统的方点外接圆点的做法,直接在方点处讨论(因为找不到看得懂的题解了) 
ll ans,dp[N],g[N],h[N];
int q[N],c[N];
//int jump(int pos,int stp){
//	for(int i=19;i>=0;--i)
//		if(stp>=(1<<i)){
//			pos=f[pos][i];
//			stp-=(1<<i);//学会倍增跳 
//		}
//	return pos;
//}
void jump(int & u, int k) {
    for(int i = 20 - 1; ~ i; --i) {
        if(k >= (1 << i)) {
            k -= 1 << i;
            u = f[u][i];
        }
    }
}
bool cmp2(int aa,int bb){
	return l[aa]>l[bb];
}
void circle(int u,int size){
//	printf("%d %d\n",u,size);
//	printf("cir=%lld\n",sum[u]);

	sort(c+1,c+size+1,cmp2);
//	reverse(c+1,c+size+1);
	//这里两种都可以,但上面一种多个log好理解
	//下面一种讲究建边细节
	//具体是我们要从环的底端到顶端,考虑vector存储,那么底端要先加进c,那么底端先访问,即底端先建边,等价于底端先出进虚树的栈,所以底端dfn要大,所以建圆方树时要先建方点到顶端依次到底端的圆点,然后tarjan处理的时候自己注意 

	for(int i=1;i<=size;++i){
//		printf("%d ",c[i]);
		h[i]=dis[c[i]];
		h[i+size]=h[i]+sum[u];//注意用的是原仙人掌上环上的值 
		g[i]=dp[c[i]];
		g[i+size]=g[i];
		//复制一遍 
//		printf("加油=%lld %lld\n",h[i],g[i]);
	}
//	puts("");
	int l,r;
	l=r=1;
	q[r]=1;
	size*=2;
	for(int i=1;i<=size;++i){
//		printf("加油=%lld %lld\n",h[i],g[i]);
	}
	for(int i=2;i<=size;++i){
		while(l<=r&&2LL*(h[i]-h[q[l]])>sum[u])++l;//最短路
		if(l<=r)ans=max(ans,g[i]+g[q[l]]+h[i]-h[q[l]]);
		while(l<=r&&g[q[r]]-h[q[r]]<=g[i]-h[i])--r;
		q[++r]=i;
	}
//	puts("");
}
void tree_dp(int u){
	dp[u]=0;
	int size=0;
	for(int v,i=0;i<edge[u].size();++i){
		v=edge[u][i];
		tree_dp(v);
	}
	//注意因为要保留栈,所以必须把递归分开写 
	for(int v,i=0;i<edge[u].size();++i){
		v=edge[u][i];
		if(u<=n){
			ans=max(ans,dp[u]+dp[v]+Len[v]-Len[u]);
			//树部分 
		}
		else{
			int son=v;
			jump(son,dep[v]-dep[u]-1);//son要么就是v要么本不存在于虚树上 
			dp[son]=dp[v]+Len[v]-Len[son];//跳到环上 
			c[++size]=son; 
		}
		dp[u]=max(dp[u],dp[v]+Len[v]-Len[u]);//圆方树上传贡献 
	}
	if(u>n)circle(u,size);//环部分
	edge[u].clear();//清空 
}
int main(){
//	freopen("s.in","r",stdin);
	n=read(),m=read();
	tot=n;
	int u,v,w;
	for(int i=1;i<=m;++i){
		u=read(),v=read(),w=read();
		add(u,v,w),add(v,u,w);
	}
	tarjan(1);
//	puts("dis");
//	for(int i=1;i<=n;++i)printf("%lld ",dis[i]);
//	puts("dis");
	dfs(1,0);
	prework();
	int Q=read();
	while(Q--){
		int size=read();
		for(int i=1;i<=size;++i)a[i]=read();
		sort(a+1,a+size+1,cmp);
		size=unique(a+1,a+size+1)-a-1;//记得去重(题目要求) 
		top=0;
		for(int i=1;i<=size;++i)build(a[i]);
		while(top>1){
			edge[s[top-1]].push_back(s[top]);
			--top;
		}
		ans=0;
		tree_dp(s[top]);//也可以不强制1为根 
//		for(int i=1;i<=size;++i)printf("%lld ",dp[i]);puts("");
//		puts("");
		printf("%lld\n",ans);
	}
	return 0;
}

UOJ87 mx的仙人掌 虚仙人掌

标签:stp   去重   比较   else   题目   ==   pac   递归调用   reverse   

原文地址:https://www.cnblogs.com/wzhh/p/14380553.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!