题目链接:

https://jzoj.net/senior/#contest/show/2529/2

题目:

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

题目大意:

一张无向图,询问两点之间的可能的路径上的最小点权,带修改

前置知识点:

圆方树相关内容


首先我们看圆方树

圆方树连边规则:

如果一条边在仙人掌中不属于任何一个环中,那么它直接圆方树中的两个圆点。

对于仙人掌中的任意一个环,则每个环上的点在圆方树上对应的圆点向这个环对应的方点连边。如下图所示

注意圆方树只适用于仙人掌

怎么构造圆方树呢?

代码实现大概是这样的

void tarjan(int x,int pre)
{
dfn[x]=low[x]=++js;sta[++tp]=x;
for (int i=h1[x];i;i=e1[i].nxt)
{
if (i==(pre^)) continue;
int y=e1[i].to;
if (!dfn[y])
{
tarjan(y,i);
low[x]=min(low[x],low[y]);
if (low[y]>dfn[x]) link2(x,y),tp--;//找到桥
if (low[y]==dfn[x])//x在边双里
{
for (++nn,link2(x,nn);sta[tp]!=x;tp--) A[nn].insert(a[sta[tp]]),link2(nn,sta[tp]);
}
}
else low[x]=min(low[x],dfn[y]);
}
}

那么广义圆方树呢?

构造方法和圆方树区别不大,就是强制把两点一线也看成一个点双,建方点即可。

广义圆方树可以适用于无向图,而不只是仙人掌


题解:

言归正传,这道题我们怎么做?

如果没有修改的话,很显然,每个方点的值为这个点双中权值最小的点。 这样,就变成了裸的求树上两点路径上最小值,剖一下就行了

考虑这样怎么修改,记录下每一个圆点连接的方点,开个set维护一下每个方点连接的原点。对于修改的原点一个个修改每个与它相连的方点,但如果这个点是一个割点(连接很多个方点),那么可能就需要修改很多次,时间复杂度就不对了

题解用了更优秀的做法

把圆方树变成一个有根树,方点的值改为记录环上除环的根外权值最小的点,这样每个圆点只有一个对应的方点。如果 LCA 是方点,只需特判一下LCA的父亲。画个图理解吧

然而代码里写的广义圆方树用的是圆方树的构造方法,身为蒟蒻的我不知道两种构造方法有什么区别...这是我第一次写圆方树

还有与圆方树类似的做法,就是对每一个边双维护一个set,每次修改就是修改这个点所在的边双即可

注意边双和点双都意味着其中的任意两个点可以经过任意一个想经过的点到达彼此

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std; const int N=2e5+;
const int inf=1e9;
int n,m,tot1=,tot2=,js,tp,nn;
int a[N],h1[N],h2[N],dfn[N],low[N],sta[N],siz[N],dd[N],id[N],dep[N],son[N],top[N],pf[N];
int mi[N<<];
multiset <int> A[N];
struct EDGE
{
int to,nxt;
}e1[N<<],e2[N<<];
inline int read()
{
char ch=getchar();
int s=,f=;
while (ch<''||ch>'') {if (ch=='-') f=-;ch=getchar();}
while (ch>=''&&ch<='') {s=(s<<)+(s<<)+ch-'';ch=getchar();}
return s*f;
}
void link1(int u,int v)
{
e1[++tot1]=(EDGE){v,h1[u]};
h1[u]=tot1;
}
void link2(int u,int v)
{
pf[v]=u;
e2[++tot2]=(EDGE){v,h2[u]};
h2[u]=tot2;
}
void tarjan(int x,int pre)//构造园方树
{
dfn[x]=low[x]=++js;sta[++tp]=x;
for (int i=h1[x];i;i=e1[i].nxt)
{
if (i==(pre^)) continue;
int y=e1[i].to;
if (!dfn[y])
{
tarjan(y,i);
low[x]=min(low[x],low[y]);
if (low[y]>dfn[x]) link2(x,y),tp--;//找到桥
if (low[y]==dfn[x])//x在点双里
{
for (++nn,link2(x,nn);sta[tp]!=x;tp--) A[nn].insert(a[sta[tp]]),link2(nn,sta[tp]);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs1(int x)
{
siz[x]=;
for (int i=h2[x];i;i=e2[i].nxt)
{
int y=e2[i].to;
if (y==pf[x]) continue;
dep[y]=dep[x]+;
dfs1(y);
siz[x]+=siz[y];
if (!son[x]||siz[y]>siz[son[x]]) son[x]=y;
}
}
void dfs2(int x,int tt)
{
dd[x]=++js;id[js]=x;
top[x]=tt;
if (!son[x]) return;
dfs2(son[x],tt);
for (int i=h2[x];i;i=e2[i].nxt)
{
int y=e2[i].to;
if (y==pf[x]||y==son[x]) continue;
dfs2(y,y);
}
}
#define mid ((l+r)>>1)
void pushup(int o) {mi[o]=min(mi[o<<],mi[o<<|]);}
void build(int o,int l,int r)
{
if (l==r) {mi[o]=a[id[l]];return;}
build(o<<,l,mid);build(o<<|,mid+,r);
pushup(o);
}
void update(int o,int l,int r,int x,int y)
{
if (l==r) {mi[o]=y;return;}
if (x<=mid) update(o<<,l,mid,x,y);
else update(o<<|,mid+,r,x,y);
pushup(o);
}
int query(int o,int l,int r,int x,int y)
{
if (l>=x&&r<=y) return mi[o];
int re=inf;
if (x<=mid) re=min(re,query(o<<,l,mid,x,y));
if (y>mid) re=min(re,query(o<<|,mid+,r,x,y));
return re;
}
int query_min(int x,int y)
{
int re=inf;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
re=min(re,query(,,nn,dd[top[x]],dd[x]));
x=pf[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
re=min(re,query(,,nn,dd[y],dd[x]));
if (y>n&&pf[y]) re=min(re,a[pf[y]]);//特判LCA是否是方点,如果是的话还需要考虑它的父亲
return re;
}
int main()
{
freopen("paoshang.in","r",stdin);
freopen("paoshang.out","w",stdout);
n=read();m=read();nn=n;
for (int i=;i<=n;i++) a[i]=read();
for (int i=,u,v;i<=m;i++)
{
u=read();v=read();
link1(u,v);link1(v,u);
}
js=;tarjan(,-);
for (int i=n+;i<=nn;i++) a[i]=*A[i].begin();
dep[]=;dfs1();
js=;dfs2(,);
build(,,nn);
int q=read(),pr,pl;
char op[];
while (q--)
{
scanf("%s",op);
if (op[]=='C')
{
int x=read(),y=read();
update(,,nn,dd[x],y);
if ((pr=pf[x])>n)
{
A[pr].erase(A[pr].find(a[x]));A[pr].insert(y);
if (a[pr]!=(pl=*A[pr].begin())) update(,,nn,dd[pr],pl),a[pr]=pl;
}
a[x]=y;
}
if (op[]=='Q')
{
int x=read(),y=read();
printf("%d\n",a[x]-query_min(x,y));
}
}
return ;
}

[JZOJ 5909] [NOIP2018模拟10.16] 跑商(paoshang) 解题报告 (圆方树)的更多相关文章

  1. [jzoj 5930] [NOIP2018模拟10.26】山花 解题报告 (质因数分类)

    题目链接: http://172.16.0.132/senior/#contest/show/2538/2 题目: 小S决定从某一个节点$u$开始对其子树中与$u$距离小于$K$的节点代表的花树进行采 ...

  2. [JZOJ 5893] [NOIP2018模拟10.4] 括号序列 解题报告 (Hash+栈+map)

    题目链接: https://jzoj.net/senior/#main/show/5893 题目: 题解: 考虑暴力怎么做,我们枚举左端点,维护一个栈,依次加入元素,与栈顶元素和栈内第二个元素相同时弹 ...

  3. [JZOJ 5905] [NOIP2018模拟10.15] 黑暗之魂(darksoul) 解题报告 (拓扑排序+单调队列+无向图基环树)

    题目链接: http://172.16.0.132/senior/#main/show/5905 题目: oi_juruo热爱一款名叫黑暗之魂的游戏.在这个游戏中玩家要操纵一名有 点生命值的无火的余灰 ...

  4. [JZOJ 5908] [NOIP2018模拟10.16] 开荒(kaihuang)解题报告 (树状数组+思维)

    题目链接: https://jzoj.net/senior/#contest/show/2529/1 题目: 题目背景:尊者神高达作为一个萌新,在升级路上死亡无数次后被一只大黄叽带回了师门.他加入师门 ...

  5. [JZOJ 5888] [NOIP2018模拟9.29] GCD生成树 解题报告 (最大生成树+公约数)

    题目链接: http://172.16.0.132/senior/#main/show/5888 题目: 题解: 思路是这样的:两个数的最大公约数一定不会比这两个数的任意一个数大.因此我们把权值相等的 ...

  6. [JZOJ 5885] [NOIP2018模拟9.27] 物理实验 解题报告 (思维)

    题目链接: https://jzoj.net/senior/#main/show/5885 题目: 题解: 把$a$数组按升序排序 我们可以枚举$x$,发现对于任意$x$,最优情况下$y$一定等于$x ...

  7. [jzoj 5926] [NOIP2018模拟10.25] naive 的图 解题报告(kruskal重构树+二维数点)

    题目链接: https://jzoj.net/senior/#main/show/5926 题目: 题解: 显然最小的最大路径在最小生成树上(最小生成树=最小瓶颈生成树) 于是我们建出kruskal重 ...

  8. [JZOJ 5911] [NOIP2018模拟10.18] Travel 解题报告 (期望+树形DP)

    题目链接: http://172.16.0.132/senior/#contest/show/2530/1 题目: EZ同学家里非常富有,但又极其的谦虚,说话又好听,是个不可多得的人才.        ...

  9. [JZOJ 5912] [NOIP2018模拟10.18] VanUSee 解题报告 (KMP+博弈)

    题目链接: https://jzoj.net/senior/#contest/show/2530/2 题目: 众所周知,cqf童鞋对哲学有着深入的理解和认识,并常常将哲学思想应用在实际生活中,例如锻炼 ...

随机推荐

  1. zzulioj--1638--Happy Thanksgiving Day - Say 3Q I(水题)

    1638: Happy Thanksgiving Day - Say 3Q I Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 91  Solved: ...

  2. Oracle回滚段的概念,用法和规划及问题的解决

    回滚段概述  回滚段用于存放数据修改之前的值(包括数据修改之前的位置和值).回滚段的头部包含正在使用的该回滚段事务的信息.一个事务只能使用一个回滚段来存放它的回滚信息,而一个回滚段可以存放多个事务的回 ...

  3. MVC、控件、一般处理程序中的session and cookie

    Mvc中: session: if (!string .IsNullOrEmpty(find)) //设置 Session["oip"] = "无锡"; Vie ...

  4. 一.Windows I/O模型之选择(select)模型

    1.选择(select)模型:选择模型:通过一个fd_set集合管理套接字,在满足套接字需求后,通知套接字.让套接字进行工作.避免套接字进入阻塞模式,进行无谓的等待.选择模型的核心的FD_SET集合和 ...

  5. [OS][ linux ] 建立新帳號, 變更密碼, 加入 sudoer

    新增 linux , 設定預設 password, 新增 user 到 sudoers 1. 新增 User sudo useradd aa97 2. 設定 User password sudo pa ...

  6. TortoiseSVN—Repo-browser

    TortoiseSVN—Repo-browser,打开你要比较的两个版本所在的地址,选择一个版本做为比较的基础(单击右键—选择mark for comparison),再选择另外一个版本(单击右键—选 ...

  7. python字符串、列表、元组

    字符串的常用方法: name.count('h')统计h在name中出现的次数 name.find('h')查找h的索引 '?'.join(name)使用问好拼接 name.encode('gb231 ...

  8. JS判断客户端是否是iOS或者Android或者ipad(二)

    js判断客户端是IPAD和iphone 多了就不说了,直接上代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22     funct ...

  9. 获取浏览器端的cookie方法

    代码如下: function getCookie(key){ var cookies=document.cookie; if(cookies.length>0){ var start=cooki ...

  10. 队列Queue的get方法

    写了一段生产者消费者模型的代码: from time import sleep from random import randint, random from multiprocessing impo ...