[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;
}