[SCOI2013]摩托车交易 题解
思路分析
为了让交易额尽量大,显然我们需要尽量多地买入。对于每个城市,到达这个城市时携带的黄金受到几个条件的影响:之前卖出的黄金,之前能买入的最多的黄金,前一个城市到当前城市的路径上的最小边权。既然不需要输出买入的数量,我们可以先尽量多地买入,然后再按照边权的限制削减。这样,刚好卖完的限制也就没有影响了。卖出时当然也要尽量都,因此卖出的量就是前一个城市能带到当前城市的最多的黄金和卖出限制中小的一个。
显然我们希望城市之间的路径上的最小边权最大,即使多绕路也没有关系。于是我们可以想到先求出一棵最大生成树,用kruskal就可以了。
然后我们需要知道最大生成树上两点之间的距离,这个距离可以用倍增或者树剖来求。
对于有列车站的城市,可以看作同一个城市。注意,买入和卖出的限制还是要按照原城市算,在求最大生成树和进行树剖处理询问时才合并。这里用有列车站的编号最小的城市来代表这些城市的代表编号。
整理一下思路,对于每个城市,我们执行以下操作:
1. 求出前一个城市最多能带到当前城市多少黄金
2. 若卖出,则输出黄金量和卖出限制中小的一个,并维护黄金量;若买入,则直接当做全部买入,维护黄金量即可。
具体实现
1. 建一棵最小生成树
跑一遍kruskal即可,就不再赘述了。
struct Edge
{
int x,y,z;
#define x(i) e[i].x
#define y(i) e[i].y
#define z(i) e[i].z
}e[2*M]; void add(int x,int y,int z)
{
ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
ver[++tot]=x,edge[tot]=z,Next[tot]=head[y],head[y]=tot;
} bool cmp(Edge a,Edge b)
{
return a.z>b.z;
} int get(int a)
{
return fa[a]==a?a:fa[a]=get(fa[a]);
} void kruskal()
{
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
{
int x=p[x(i)]?minp:x(i),y=p[y(i)]?minp:y(i),fx,fy;//看看能否合并
fx=get(x),fy=get(y);
if(fx==fy)
continue;
fa[fx]=fy;
add(x,y,z(i));
}
}
2. 树剖预处理
正常的树剖预处理,这里用每条边两个端点中深度更大的节点存储这条边的权值,方便处理。对树链剖分不熟悉的可以点击食用。
struct Tree
{
int siz,f,d,seg,son,top;
#define siz(i) t[i].siz
#define f(i) t[i].f
#define d(i) t[i].d
#define seg(i) t[i].seg
#define son(i) t[i].son
#define top(i) t[i].top
}t[N]; struct Seg
{
int l,r,rev;
ll sum;
#define l(i) c[i].l
#define r(i) c[i].r
#define sum(i) c[i].sum
#define rev(i) c[i].rev
}c[N]; void dfs1(int f,int x)
{
siz(x)=1,d(x)=d(f)+1,f(x)=f;
for(int i=head[x];i;i=Next[i])
if(ver[i]!=f)
{
int y=ver[i];
dfs1(x,y);
siz(x)+=siz(y);
if(siz(y)>siz(son(x)))
son(x)=y;
len[y]=edge[i];//用深度较大的端点存储边的权值
}
} void dfs2(int x)
{
if(son(x))
{
seg(son(x))=++seg(0);
rev(seg(0))=son(x);
top(son(x))=top(x);
dfs2(son(x));
}
for(int i=head[x];i;i=Next[i])
if(!top(ver[i]))
{
int y=ver[i];
seg(y)=++seg(0);
rev(seg(0))=y;
top(y)=y;
dfs2(y);
}
} void build(int p,int l,int r)
{
l(p)=l,r(p)=r;
if(l==r)
{
sum(p)=len[rev(l)];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid),build(rson,mid+1,r);
sum(p)=min(sum(lson),sum(rson));
} seg(1)=++seg(0),rev(1)=1,top(1)=1,len[1]=IINF;
3. 依次对每个城市进行处理
先算出上一个城市到当前城市最多能携带多少黄金,若卖出,则取携带量和卖出限制中小的一个卖出;若买入,则全部买入。
有一点需要注意的,因为是用深度较大的端点存储边的权值,因此查询的时候不能查询到LCA处,因为LCA存储的边权不在路径上。
ll op(int x,int y)
{
ll val=IINF;
x=p[x]?minp:x,y=p[y]?minp:y;//看看能否合并
while(top(x)!=top(y))
{
if(d(top(x))<d(top(y)))
swap(x,y);
val=min(val,ask(1,seg(top(x)),seg(x)));
x=f(top(x));
}
if(d(x)>d(y))
swap(x,y);
val=min(val,ask(1,seg(x)+1,seg(y)));//不能查询到LCA处
return val;
} void solve()
{
for(int i=1;i<=n;i++)
{
now=min(i!=1?op(rank[i-1],rank[i]):IINF,now);//查询上一个城市到当前城市的路径上最小边权
if(w[rank[i]]<0)
{
printf("%lld\n",min(-w[rank[i]],now));//卖出较小的一个
now-=min(-w[rank[i]],now);//维护剩下的黄金
}
else
now+=w[rank[i]];//全部买入
}
}
最后奉上完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define lson p<<1
#define rson p<<1|1
#define ll long long
using namespace std;
const int N=1e6,M=1e6,INF=1e8;
const ll IINF=1e17;
int n,m,q,tot,minp=INF;
ll now;
int head[N],ver[2*M],Next[2*M];
int rank[N],fa[N];
ll edge[2*M];
ll w[N],len[N];
bool p[N];
struct Edge
{
int x,y,z;
#define x(i) e[i].x
#define y(i) e[i].y
#define z(i) e[i].z
}e[2*M];
struct Tree
{
int siz,f,d,seg,son,top;
#define siz(i) t[i].siz
#define f(i) t[i].f
#define d(i) t[i].d
#define seg(i) t[i].seg
#define son(i) t[i].son
#define top(i) t[i].top
}t[N];
struct Seg
{
int l,r,rev;
ll sum;
#define l(i) c[i].l
#define r(i) c[i].r
#define sum(i) c[i].sum
#define rev(i) c[i].rev
}c[N];
bool cmp(Edge a,Edge b)
{
return a.z>b.z;
}
void add(int x,int y,int z)
{
ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
ver[++tot]=x,edge[tot]=z,Next[tot]=head[y],head[y]=tot;
}
void dfs1(int f,int x)
{
siz(x)=1,d(x)=d(f)+1,f(x)=f;
for(int i=head[x];i;i=Next[i])
if(ver[i]!=f)
{
int y=ver[i];
dfs1(x,y);
siz(x)+=siz(y);
if(siz(y)>siz(son(x)))
son(x)=y;
len[y]=edge[i];
}
}
void dfs2(int x)
{
if(son(x))
{
seg(son(x))=++seg(0);
rev(seg(0))=son(x);
top(son(x))=top(x);
dfs2(son(x));
}
for(int i=head[x];i;i=Next[i])
if(!top(ver[i]))
{
int y=ver[i];
seg(y)=++seg(0);
rev(seg(0))=y;
top(y)=y;
dfs2(y);
}
}
void build(int p,int l,int r)
{
l(p)=l,r(p)=r;
if(l==r)
{
sum(p)=len[rev(l)];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid),build(rson,mid+1,r);
sum(p)=min(sum(lson),sum(rson));
}//2. 树剖预处理
ll ask(int p,int l,int r)
{
if(l<=l(p) && r(p)<=r)
return sum(p);
int mid=(l(p)+r(p))>>1;
ll val=IINF;
if(l<=mid)
val=min(val,ask(lson,l,r));
if(r>mid)
val=min(val,ask(rson,l,r));
return val;
}
int get(int a)
{
return fa[a]==a?a:fa[a]=get(fa[a]);
}
void kruskal()
{
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
{
int x=p[x(i)]?minp:x(i),y=p[y(i)]?minp:y(i),fx,fy;
fx=get(x),fy=get(y);
if(fx==fy)
continue;
fa[fx]=fy;
add(x,y,z(i));
}
}//1. 建一棵最小生成树
ll op(int x,int y)
{
ll val=IINF;
x=p[x]?minp:x,y=p[y]?minp:y;
while(top(x)!=top(y))
{
if(d(top(x))<d(top(y)))
swap(x,y);
val=min(val,ask(1,seg(top(x)),seg(x)));
x=f(top(x));
}
if(d(x)>d(y))
swap(x,y);
val=min(val,ask(1,seg(x)+1,seg(y)));
return val;
}
void read()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
scanf("%d",&rank[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&x(i),&y(i),&z(i));
for(int i=1;i<=q;i++)
{
int x;
scanf("%d",&x);
p[x]=1;minp=min(minp,x);
}
}
void solve()
{
for(int i=1;i<=n;i++)
{
now=min(i!=1?op(rank[i-1],rank[i]):IINF,now);
if(w[rank[i]]<0)
{
printf("%lld\n",min(-w[rank[i]],now));
now-=min(-w[rank[i]],now);
}
else
now+=w[rank[i]];
}
}//3. 依次对每个城市进行处理
int main()
{
read();
kruskal();
seg(1)=++seg(0),rev(1)=1,top(1)=1,len[1]=IINF;
dfs1(0,1),dfs2(1),build(1,1,n);
solve();
return 0;
}
[SCOI2013]摩托车交易 题解的更多相关文章
- BZOJ3322[Scoi2013]摩托车交易——最大生成树+贪心+倍增
题目描述 mzry1992 在打完吊针出院之后,买了辆新摩托车,开始了在周边城市的黄金运送生意.在mzry1992 生活的地方,城市之间是用双向高速公路连接的.另外,每条高速公路有一个载重上限,即在不 ...
- 【[SCOI2013]摩托车交易 】
倍增什么的最慢了,常数太大了 我们可以上树剖啊 但是如果用树剖来查询树上两点之间的最小边权的话,可能只能在上一棵线段树? 那也太\(naive\)了,尽管倍增常数大,但是还是比两个\(log\)快的 ...
- [SCOI2013]摩托车交易 kruskal重构树(最大生成树) 倍增
---题面--- 题解: 这题想法简单,,,写起来真的是失智,找了几个小时的错误结果是inf没开到LL范围.... 首先我们需要找到任意两点之间能够携带黄金的上限值,因为是在经过的道路权值中取min, ...
- BZOJ3322 : [Scoi2013]摩托车交易
求出最大生成树,则两点间的最大容量为树上两点间的边权的最小值. 设$lim[i]$表示第$i$个订单的城市允许携带的黄金上限,则 $lim[i]=\min(lim[i+1],a[i]和a[i+1]点间 ...
- 2019.03.28 bzoj3322: [Scoi2013]摩托车交易(kruskal重构树+贪心)
传送门 题意咕咕咕 思路: 先把所有可以列车通的缩成一个点,然后用新图建立kruskalkruskalkruskal重构树. 这样就可以倒着贪心模拟了. 代码: #include<bits/st ...
- 【SCOI2013】摩托车交易 - 最大生成树+树链剖分
题目描述 mzry1992 在打完吊针出院之后,买了辆新摩托车,开始了在周边城市的黄金运送生意.在mzry1992 生活的地方,城市之间是用双向高速公路连接的.另外,每条高速公路有一个载重上限,即在不 ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- BZOJ3323: [Scoi2013]多项式的运算
3323: [Scoi2013]多项式的运算 Time Limit: 12 Sec Memory Limit: 64 MBSubmit: 128 Solved: 33[Submit][Status ...
- toodifficult 题解
名字听起来十分厉害啊...一道lzz的提交答案题. 提答题,我们看看题目,给出一个解密程序,叫你加密. 每个点有一个加密的sample和一些要加密的文本. 从题目中我们可以得到一些信息: 加密后一般为 ...
随机推荐
- Skill 脚本演示 ycBusNet.il
https://www.cnblogs.com/yeungchie/ ycBusNet.il 用于原理图中按照一定格式,批量创建 Bus . 回到目录
- 2020牛客暑期多校训练营 第二场 I Interval 最大流 最小割 平面图对偶图转最短路
LINK:Interval 赛时连题目都没看. 观察n的范围不大不小 而且建图明显 考虑跑最大流最小割. 图有点稠密dinic不太行. 一个常见的trick就是对偶图转最短路. 建图有点复杂 不过建完 ...
- C/C++编程笔记:C语言制作情侣必备《爱情电子相册》,源码解析!
今天是521,就分享一个程序员必会的——情侣回忆杀<爱情电子相册>吧!话不多说,先上思路,后接源码! 具备能力: 1.基本可视化编程 1.1 initgraph(800,600); 1.2 ...
- 什么是Cookie、Session、Token?
原文:https://mp.weixin.qq.com/s/pWXhI_ppKhtOP-Xf_SpuDA 来源:后厂村码农 在了解这三个概念之前我们先要了解 HTTP 是无状态的Web服务器,什么是无 ...
- Sharding-JDBC主键生成策略
当使用分库分表等功能之后,就不能再依赖数据库自带的主键生成机制了,一方面主键ID不能重复,另外需要在新增之前就知道主键ID,才能保证ID能够均匀分布到不同的数据库或数据表中,所以要使用一个合理的主键生 ...
- CF习题集二
CF习题集二 一.CF507E Breaking Good 题目描述 \(Breaking Good\)这个游戏对于有经验的玩家来说也有一定的难度. 游戏的主角小明希望加入一个叫斧头帮的犯罪团伙.这个 ...
- java交换两个参数值的四种方法
第一种:添加中间变量,算是最经典最简易的一种了. //添加一个中间变量 int x = 1, y = 2; int z; z = x;x = y;y = z; System.out.println(x ...
- 44-final, finally, finalize的区别
final—修饰符(关键字) 如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承. 因此一个类不能既被声明为 abstract的,又被声明为final的.将变量或方法声明为 ...
- 漫画 | 到底是什么让IT人如此苦逼???
写在最后 漫画是有点夸张,不过多少还是有点现实开发过程的影子! 老板很乐观,核心就是三个月上线,至于怎么办那是底下人的事. 产品很无奈,心里盘算着,万万不可在他这一环节耽误了进度,时间这么赶,先出个壳 ...
- Markdown基本语法及生成目录结构的方法
Markdown是一种纯文本格式的标记语言.通过简单的标记语法,它可以使普通文本内容具有一定的格式. 一.标题 在想要设置为标题的文字前面加#来表示一个#是一级标题,二个#是二级标题,以此类推.支持六 ...