Description
您有一个有向完全图,其中 \(u \rightarrow v\) 的边权 \(a_{u}\) 。
将会有 \(m\) 次修改,第 \(i\) 次修改会给定 \(x, y, z\) ,并将 \(x \rightarrow y\) 的有向边的边权改为 \(z\) 。
求所有点对 \(i, j(i \neq j)\) 间最短路之和。
\(n\le 10^5,m\le 3000\)。
Solution
注意到更改最多只有 3000 次,意味着将会有很多点未被更改,我们记这些没有被更改过的点(无论是连出去还是连进来)为普通点。那么对于任意两个普通点之间的距离是很容易算的。
现在考虑特殊点,将所有特殊点放进一个队列,同时将 \(a_i\) 最小的普通点也放进队列。但是 \(a_i\) 本身的答案已经计算过了,这里是因为考虑到存在特殊点 \(\rightarrow\) 普通点 \(\rightarrow\) 特殊点的特殊情况。
遍历这个序列,以每个特殊点为起点跑 \(\text{dij}\),时间复杂度 \(\mathcal O(n^3)\)。
考虑优化,由于总边数不大,那么对于每个特殊点,连出去的特殊边不会特别多,因此可以采取线段树优化。对于起点这个特殊点,找出与它有连边的特殊点,那么两个特殊点之间的贡献是相同的,可以区间修改,然后对于有连边的特殊点单点修改即可。优化到 \(\mathcal O(n^2\log{n})\)。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define inf 1145141919810
#define ll long long
using namespace std;
struct node
{
int to,next,head;
ll val;
}edge[N<<1],son[N];
struct seg
{
int id;
ll lazy,mn;
}tree[N<<2];
int n,m,cnt,tot,flag,a[N],x[N],y[N],z[N],id[N],c[N];
ll ans;
bool bj[N];
void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].val=z;
edge[tot].next=edge[x].head;
edge[x].head=tot;
}
bool cmp(node x,node y) {return x.to<y.to;}
void pushdown(int x)
{
if (!bj[x<<1])
{
tree[x<<1].mn=min(tree[x<<1].mn,tree[x].lazy);
if (tree[x<<1].lazy!=-1) tree[x<<1].lazy=min(tree[x<<1].lazy,tree[x].lazy);
else tree[x<<1].lazy=tree[x].lazy;
}
if (!bj[x<<1|1])
{
tree[x<<1|1].mn=min(tree[x<<1|1].mn,tree[x].lazy);
if (tree[x<<1|1].lazy!=-1) tree[x<<1|1].lazy=min(tree[x<<1|1].lazy,tree[x].lazy);
else tree[x<<1|1].lazy=tree[x].lazy;
}
}
void pushup(int x)
{
ll lz=tree[x].lazy;
if (bj[x<<1]&&bj[x<<1|1]) bj[x]=true;
if ((tree[x<<1].mn<tree[x<<1|1].mn&&!bj[x<<1])||bj[x<<1|1]) tree[x]=tree[x<<1];
else tree[x]=tree[x<<1|1];
tree[x].lazy=lz;
}
void build(int now,int l,int r)
{
tree[now].id=l;
tree[now].lazy=-1;
tree[now].mn=inf;
if (l==r) return;
int mid=(l+r)>>1;
build(now<<1,l,mid);build(now<<1|1,mid+1,r);
}
void modify(int now,int l,int r,int p,int q,ll v)
{
if (tree[now].lazy!=-1)
{
if (tree[now].lazy<=v) return;
else pushdown(now);
}
if (l>=p&&r<=q)
{
if (tree[now].lazy==-1) tree[now].lazy=v;
else tree[now].lazy=min(tree[now].lazy,v);
tree[now].mn=min(tree[now].mn,v);
return;
}
int mid=(l+r)>>1;
if (p<=mid&&q>=l&&!bj[now<<1]) modify(now<<1,l,mid,p,q,v);
if (p<=r&&q>mid&&!bj[now<<1|1]) modify(now<<1|1,mid+1,r,p,q,v);
pushup(now);
}
ll query(int now,int l,int r)
{
if (l==r)
{
if (r==cnt&&flag) return tree[now].mn*(ll)flag;
else return tree[now].mn;
}
int mid=(l+r)>>1;
return query(now<<1,l,mid)+query(now<<1|1,mid+1,r);
}
void mark(int now,int l,int r,int p)
{
if (tree[now].lazy!=-1) pushdown(now);
if (l==r)
{
bj[now]=1;
return;
}
int mid=(l+r)>>1;
if (p<=mid) mark(now<<1,l,mid,p);
else mark(now<<1|1,mid+1,r,p);
pushup(now);
}
void dij(int x)
{
memset(bj,false,sizeof(bj));
build(1,1,cnt);
modify(1,1,cnt,x,x,0);
int t=0;
while (t<cnt)
{
ll res=tree[1].mn;
int now=tree[1].id,num=0;
mark(1,1,cnt,now);
for (int i=edge[now].head;i;i=edge[i].next)
{
son[++num].to=edge[i].to;
son[num].val=edge[i].val;
}
sort(son+1,son+num+1,cmp);
for (int i=1;i<=num;++i)
{
modify(1,1,cnt,son[i-1].to+1,son[i].to-1,res+c[now]);
modify(1,1,cnt,son[i].to,son[i].to,res+son[i].val);
}
if (son[num].to+1<=cnt) modify(1,1,cnt,son[num].to+1,cnt,res+c[now]);
++t;
}
ans+=query(1,1,cnt);
}
int main()
{
freopen("happybean.in","r",stdin);
freopen("happybean.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int i=1;i<=m;++i)
{
scanf("%d%d%d",&x[i],&y[i],&z[i]);
bj[x[i]]=bj[y[i]]=true;
}
for (int i=1;i<=n;++i)
if (bj[i]) id[i]=++cnt,c[cnt]=a[i];
for (int i=1;i<=m;++i)
add(id[x[i]],id[y[i]],z[i]);
if (cnt!=n)
{
++cnt;
int k=0;
for (int i=1;i<=n;++i)
if (!bj[i])
{
id[i]=cnt;
if (a[i]<a[k]||!k) k=i;
ans+=a[i];
flag++;
}
c[cnt]=a[k];
ans=(ll)ans*(n-1);
}
for (int i=1;i<cnt;++i)
dij(i);
if (!flag) dij(cnt);
printf("%lld\n",ans);
return 0;
}