JZOJ 5909. 【NOIP2018模拟10.16】跑商(paoshang)

38 篇文章 0 订阅
9 篇文章 0 订阅

Description

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

Input

第一行 n,m;
接下来 1 行 n 个数,代表每个城市货物的价格;
接下来 m 行 u,v 代表一条边
接下来 1 行 Q
接下来 Q 行
C x w 代表城市 x 的货物价格变为 w
Q u v 代表一次从 u 到 v 的跑商

Output

如题目描述

Sample Input

3 3
1 2 3
1 2
2 3
1 3
3
Q 2 3
C 1 5
Q 1 3

Sample Output

1
3

样例解释:
1,2,3 都联通,起点购买价格为 2,在 1 点卖出赔得最多2-1=1
更新后每个点价值为 5,2,3
起点价格为 5,在 2 点卖出赔得最多,5-2=3

Data Constraint

40%的数据为一棵树
另外 20%的数据没有修改操作
所以数据满足 n,m,q<=100000;保证图联通,数据合法

Solution

  • 40%的数据就是裸的树链剖分,查询区间最小值、单点修改即可。

  • 而变成无向图后呢,就要用到圆方树了!

  • 圆方树是处理仙人掌的利器,应用广泛。

  • 嗯,首先,它是一棵树,由圆点和方点组成。

  • 原图中就存在的点被称为圆点,我们将其缩点双联通分量后,新建一个方点来代表这个点双,

  • 并将点双中的点与这个方点连边(原来点双里面的边就不要了),缩点双用 tarjan 算法。

  • 建出来的圆方树的圆点和方点是相隔的,即不会有圆点连圆点、方点连方点。

  • 有了这颗圆方树,我们就能解决很多问题了!

  • 在本题中,圆点的权值就设为其本来的值(即 a i a_i ai),而方点的值就是点双中点权的最小值。

  • 注意: 这里方点的值不包括其父亲圆点的(这样好算很多)。

  • 于是查询就能用树链剖分求最小值了,注意若两点lca是方点,还得算上其父亲圆点的值。

  • 而查询的话,对于该圆点就单点修改,对于其属于的方点就用一个set(可维护插入删除,求最小值的数据结构)维护其包含的圆点值即可。

  • 时间复杂度 O ( n   l o g 2 n ) O(n\ log^2n) O(n log2n)

Code

#include<cstdio>
#include<algorithm>
#include<set>
#include<cctype>
using namespace std;
const int N=1e5+5,inf=1e9;
int n,m,tot=1,num,cnt,rt,qx,qy,stop,ans;
int first[N],nex[N<<1],en[N<<1];
int first1[N<<1],nex1[N<<1],en1[N<<1],d[N<<1];
int a[N];
int dfn[N],low[N],st[N],id[N];
int fa[N<<1],dep[N<<1],size[N<<1],son[N<<1];
int top[N<<1],tree[N<<1],pre[N<<1],f[N<<3];
multiset<int>ss[N<<1];
multiset<int>::iterator it;
inline int read()
{
	int X=0,w=0; char ch=0;
	while(!isdigit(ch)) w|=ch=='-',ch=getchar();
	while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();
	return w?-X:X;
}
inline void insert(int x,int y)
{
	nex[++tot]=first[x];
	first[x]=tot;
	en[tot]=y;
}
inline void insert1(int x,int y)
{
	nex1[++tot]=first1[x];
	first1[x]=tot;
	en1[tot]=y;
	d[y]++;
}
inline int min(int x,int y)
{
	return x<y?x:y;
}
void tarjan(int x,int y)
{
	dfn[x]=low[x]=++num;
	st[++stop]=x;
	for(int i=first[x];i;i=nex[i])
		if(!dfn[en[i]])
		{
			tarjan(en[i],i^1);
			low[x]=min(low[x],low[en[i]]);
			if(low[en[i]]>=dfn[x])
			{
				cnt++;
				do
				{
					id[st[stop]]=cnt;
					insert1(cnt,st[stop]);
					ss[cnt].insert(a[st[stop]]);
				}while(st[stop--]^en[i]);
				insert1(x,cnt);
			}
		}else
			if(i^y) low[x]=min(low[x],dfn[en[i]]);
}
void dfs(int x)
{
	dep[x]=dep[fa[x]]+1;
	size[x]=1;
	for(int i=first1[x];i;i=nex1[i])
		if(en1[i]^fa[x])
		{
			fa[en1[i]]=x;
			dfs(en1[i]);
			size[x]+=size[en1[i]];
			if(!son[x] || size[son[x]]<size[en1[i]]) son[x]=en1[i];
		}
}
void dfs1(int x,int y)
{
	top[pre[tree[x]=++num]=x]=y;
	if(!son[x]) return;
	dfs1(son[x],y);
	for(int i=first1[x];i;i=nex1[i])
		if(en1[i]^fa[x] && en1[i]^son[x]) dfs1(en1[i],en1[i]);
}
void make(int v,int l,int r)
{
	if(l==r)
	{
		if(pre[l]>n)
		{
			it=ss[pre[l]].begin();
			f[v]=*it;
		}else f[v]=a[pre[l]];
		return;
	}
	int mid=l+r>>1;
	make(v<<1,l,mid);
	make(v<<1|1,mid+1,r);
	f[v]=min(f[v<<1],f[v<<1|1]);
}
int find(int v,int l,int r)
{
	if(qx<=l && r<=qy) return f[v];
	int mid=l+r>>1;
	int s=inf;
	if(qx<=mid) s=find(v<<1,l,mid);
	if(qy>mid) s=min(s,find(v<<1|1,mid+1,r));
	return s;
}
void change(int v,int l,int r)
{
	if(l==r)
	{
		f[v]=qy;
		return;
	}
	int mid=l+r>>1;
	if(qx<=mid) change(v<<1,l,mid); else change(v<<1|1,mid+1,r);
	f[v]=min(f[v<<1],f[v<<1|1]);
}
int main()
{
	freopen("paoshang.in","r",stdin);
	freopen("paoshang.out","w",stdout);
	n=cnt=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=read();
		insert(x,y);
		insert(y,x);
	}
	tarjan(1,tot=0);
	for(int i=1;i<=cnt;i++)
		if(!d[i])
		{
			rt=i;
			break;
		}
	dfs(rt);
	num=0;
	dfs1(rt,rt);
	make(1,1,num);
	int q=read();
	while(q--)
	{
		char ch=getchar();
		while(ch^'Q' && ch^'C') ch=getchar();
		if(ch=='Q')
		{
			int x=read(),y=read(),st=a[x];
			ans=inf;
			int f1=top[x],f2=top[y];
			while(f1^f2)
			{
				if(dep[f1]<dep[f2]) swap(f1,f2),swap(x,y);
				qx=tree[f1],qy=tree[x];
				ans=min(ans,find(1,1,num));
				x=fa[f1],f1=top[x];
			}
			if(dep[x]>dep[y]) swap(x,y);
			qx=tree[x],qy=tree[y];
			ans=min(ans,find(1,1,num));
			if(x>n) ans=min(ans,a[fa[x]]);
			printf("%d\n",st-ans);
		}else
		{
			int x=read(),w=read();
			qx=tree[x],qy=w;
			change(1,1,num);
			int y=id[x];
			if(y)
			{
				it=ss[y].find(a[x]);
				ss[y].erase(it);
				ss[y].insert(w);
				it=ss[y].begin();
				qx=tree[y],qy=*it;
				change(1,1,num);
			}
			a[x]=w;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值