7377. 【2021.11.11NOIP提高组联考】欢乐豆

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;
}
原文地址:https://www.cnblogs.com/Livingston/p/15577307.html