5909. 【NOIP2018模拟10.16】跑商(圆方树+树链剖分+SET)

1 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

基三的地图可以看做 n 个城市,m 条边的无向图,尊者神高达会从任意一个点出发并在起点购买货物,在旅途中任意一点卖出并最终到达终点,尊者神高达的时间很宝贵,所以他不会重复经过同一个城市,但是为了挣钱,他可能会去绕路。当然,由于工作室泛滥,所以一个城市的货物价格可能会发生改变。但是尊者神高达智商不足,他可能在一个很蠢的节点把货物卖掉,所以尊者神高达想知道每一次跑商最多能赔多少钱。

思路:

很容易想到答案为起点的值剪掉路径上最大值,然后问题就变成了求动态求路径最大值,如果是树的话直接树链剖分就好了,不是树的话就很难处理,我们机智的发现,一个环每条边都是可以走到的,所以我们可以用圆方树,方点为环上的最大值,然后发现好像维护不了,那我们改改定义,方点为儿子里面的最大值,这样如果lca是一个方点就还要特判断一下父亲节点。用平衡树来维护每个点的值。

程序:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;
const int N=400005;
struct tree{int to,next;}e[N*2];
struct tree1{int to,next;}e1[N*2];
int n,n1,n2,cnt,p,m,q,cnt1;
int last[N],pr[N],f[N],pt[N],st[N],size[N],son[N],ft[2*N],mi[N],dfn[N],last1[N],low[N],dep[N*2],top[N*2];
int t[N*2][2];
multiset<int> h[N];
void add(int x,int y){
	e[++cnt].to=y; e[cnt].next=last[x]; last[x]=cnt;
	e[++cnt].to=x; e[cnt].next=last[y]; last[y]=cnt;
}

void add1(int x,int y){
	e1[++cnt1].to=y; e1[cnt1].next=last1[x]; last1[x]=cnt1;
	e1[++cnt1].to=x; e1[cnt1].next=last1[y]; last1[y]=cnt1; 
}

void tarjan(int x,int fa){
	st[++st[0]]=x;
	low[x]=dfn[x]=++dfn[0];
	for(int i=last[x];i;i=e[i].next)
	if (e[i].to!=fa){
		if (!dfn[e[i].to]){
			tarjan(e[i].to,x);
			if (low[e[i].to]>=dfn[x]){
				add1(++n1,x); ;
				while (st[st[0]]!=e[i].to) add1(st[st[0]],n1),st[st[0]--]=0;
				add1(n1,e[i].to),st[st[0]--]=0;
			}
			else low[x]=min(low[x],low[e[i].to]);
		} else	low[x]=min(low[x],dfn[e[i].to]);
	}
}

void dfs(int x,int fa){
	if (x<=n&&fa>n) h[fa].insert(pr[x]);
	dep[x]=dep[fa]+1;
	f[x]=fa;
	size[x]=1;
	for (int i=last1[x];i;i=e1[i].next)
	if (e1[i].to!=fa){
		dfs(e1[i].to,x);
		size[x]+=size[e1[i].to];
		if (size[e1[i].to]>size[son[x]]) son[x]=e1[i].to;  
	}
}

void build(int x,int l,int r){
	mi[x]=1e9;
	if (l==r) pt[l]=x;
	else {
		int mid=(l+r)>>1;
		ft[t[x][0]=++n2]=x,build(n2,l,mid);
		ft[t[x][1]=++n2]=x,build(n2,mid+1,r);
	}
}

void ins(int x,int v){
	mi[x]=v,x=ft[x];
	while (x) mi[x]=min(mi[t[x][0]],mi[t[x][1]]),x=ft[x];
}

int query(int rt,int l,int r,int x,int y){
	if (!rt||x>y||x>r||y<l) return 1e9;
	if (x<=l&&r<=y) return mi[rt];
	int mid=(l+r)>>1;
	return min(query(t[rt][0],l,mid,x,y),query(t[rt][1],mid+1,r,x,y));
}

void make(int x){
	if (!x) return;
	dfn[x]=++dfn[0];
	if (x>n) pr[x]=*h[x].begin();
	ins(pt[dfn[x]],pr[x]);
	top[son[x]]=top[x],make(son[x]);
	for (int i=last1[x];i;i=e1[i].next)
	if (e1[i].to!=f[x]&&e1[i].to!=son[x]){
		top[e1[i].to]=e1[i].to,make(e1[i].to);
	}
}

int get(int x,int y){
	if (top[x]==top[y]){
		if (dep[x]>dep[y]) swap(x,y);
		int v=1e9;
		if (x>n) v=pr[f[x]];
		return min(v,query(1,1,n1,dfn[x],dfn[y]));
	}
	if (dep[top[x]]>dep[top[y]]) swap(x,y);
	return min(get(x,f[top[y]]),query(1,1,n1,dfn[top[y]],dfn[y]));
}

int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
//	freopen("paoshang.in","r",stdin);
//	freopen("paoshang.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&pr[i]);
	for (int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	n1=n;
	tarjan(1,0);
	dfs(1,0);
	n2=1;
	memset(dfn,0,sizeof(dfn));
	build(1,1,n1);
	make(1);
	scanf("%d",&q);
	for (int i=1;i<=q;i++){
		char ch;
		scanf("\n%c",&ch);
		int x,y;
		scanf("%d%d",&x,&y);
		if (ch=='Q') printf("%d\n",pr[x]-get(x,y));
		else{
			ins(pt[dfn[x]],y);
			if (x>1){
				int p=f[x];
				h[p].erase(h[p].find(pr[x]));
				h[p].insert(y);
				int v=*h[p].begin();
				if (v!=pr[p]){
					pr[p]=v;
					ins(pt[dfn[p]],pr[p]);
				}
			}
			pr[x]=y;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值