CodeForces - 787D.Legacy (线段树辅助建图跑最短路)

又是一道cf2300的题目 难怪感觉这专题刷不下去了
那咋办嘛 硬着头皮刷呗
cf1400分选手对这种题当然是一脸懵逼
看题目有区间修改 觉得要用上线段树
要求点的最小距离 感觉要用最短路
看题解之后才晓得使用线段树辅助建图来跑最短路
就很神奇 开了眼界 原来线段树这种东西还能用来建图
找了好久的题解 在了好多篇博客 最后看了两篇写的很详细的博客才看懂
然后发现这两篇是两个集训队学长写的
不会涨阅读量就是他们拉这些难题的目的吧(手动滑稽)

那两篇的博客链接
https://blog.csdn.net/weixin_43741224/article/details/96473255
有很详细的思路解释并且附有图很好理解 但是写法是用的结构体
本菜鸡有点看不懂所以还找了下面这篇博客
https://blog.csdn.net/qq_43906000/article/details/102256830
是数组写法

此图即为建树的方向 上面是a树 下面是b树
图片来源于上面的第一个博客链接
在这里插入图片描述

ac代码

 #include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <utility>
#define pi 3.1415926535898
#define ll long long
#define lson rt<<1
#define rson rt<<1|1
#define eps 1e-6
#define ms(a,b) memset(a,b,sizeof(a))
#define legal(a,b) a&b
#define print1 printf("111\n")
using namespace std;
const int maxn = 1e5+10;
const int inf = 0x1f1f1f1f;
const ll llinf =1e17+10;//因为dis数组是ll 所以重新定义了一个inf 之前还因为inf小了 wa了一发
const int mod = 2333;

//以下四个数组 从左往右 分别记录 a树每个点的标号 b树每个点的标号,a树叶子节点对应的标号,b树叶子节点对应的标号
int ta[maxn<<2],tb[maxn<<2],pa[maxn],pb[maxn];
//因为这次见图含有了两棵树上的边和一些后面加的边 所以数组要开大一些 不然re
int cnt,sz=0,first[maxn*20],vis[maxn<<3];
ll dis[maxn<<3];
int n,q,s;

//之后是链式前向星建图和dij算法的板子 没什么好说的
struct node
{
    int to,next;
    ll v;
}e[maxn*20];

struct point
{
    int id;
    ll val;
    point(int id,ll val)
    {
        this->id=id;
        this->val=val;
    }
    bool operator<(const point &x)const
    {
        return val>x.val;
    }
};

void add(int u,int v,ll w)
{
    e[cnt].to=v;
    e[cnt].next=first[u];
    e[cnt].v=w;
    first[u]=cnt++;
}

void dij(int s)
{
    ms(vis,0);
    for(int i=0;i<=n*8;i++)
        dis[i]=llinf;
    priority_queue<point>q;
    q.push(point(s,0));
    dis[s]=0;
    while(!q.empty())
    {
        int rt=q.top().id;
        q.pop();
        if(vis[rt])continue;
        vis[rt]=1;
        for(int i=first[rt];i!=-1;i=e[i].next)
        {
            int id=e[i].to;
            if(!vis[id]&&dis[rt]+e[i].v<dis[id])
            {
                dis[id]=dis[rt]+e[i].v;
                q.push(point(id,dis[id]));
            }
        }
    }
}


//线段树的操作有许多变化
void builda(int rt,int l,int r)
{
    ta[rt]=++sz;//一一记录节点标号
    if(l==r)
    {
        pa[l]=ta[rt];//记录叶子节点标号
        return;
    }
    int mid=(l+r)>>1;
    builda(lson,l,mid);
    builda(rson,mid+1,r);
    add(ta[rt],ta[lson],0);//讲树上的边建好
    add(ta[rt],ta[rson],0);
}

void buildb(int rt,int l,int r)
{
    tb[rt]=++sz;
    if(l==r)
    {
        pb[l]=tb[rt];
        add(pa[l],tb[rt],0);//我们建树时是从a树的根像b树的根建树 见上图 数所以拿a树叶子节点连到b树上
        return;
    }
    int mid=(l+r)>>1;
    buildb(lson,l,mid);
    buildb(rson,mid+1,r);
    add(tb[lson],tb[rt],0);
    add(tb[rson],tb[rt],0);
}
 //注意之后的建边都是从b树连到a树上 我们跑dij也应该是从b树往a树跑 否则话根据我们的线段树建图方向所有的值都会是零 可以根据上图进行理解
void updatatwo(int L,int R,int l,int r,int rt,ll v,int pos)
{
    if(L<=l&&r<=R)
    {
        add(pb[pos],ta[rt],v);//将b树叶子结点连至a树区间结点
       
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)
        updatatwo(L,R,l,mid,lson,v,pos);
    if(R>mid)
        updatatwo(L,R,mid+1,r,rson,v,pos);
}

void updatathree(int L,int R,int l,int r,int rt,ll v,int pos)
{
    if(L<=l&&r<=R)
    {
        add(tb[rt],pa[pos],v);//将b树区间结点连至a树叶子结点
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)
        updatathree(L,R,l,mid,lson,v,pos);
    if(R>mid)
        updatathree(L,R,mid+1,r,rson,v,pos);
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    scanf("%d%d%d",&n,&q,&s);
    ms(first,-1);
    cnt=0;
    builda(1,1,n);
    buildb(1,1,n);
    while(q--)
    {
        int type,v,u,l,r;
        ll w;
        scanf("%d",&type);
        if(type==1)
        {
            scanf("%d%d%lld",&v,&u,&w);
            add(pb[v],pa[u],w);
        }
        if(type==2)
        {
            scanf("%d%d%d%lld",&v,&l,&r,&w);
            updatatwo(l,r,1,n,1,w,v);
        }
        if(type==3)
        {
            scanf("%d%d%d%lld",&v,&l,&r,&w);
            updatathree(l,r,1,n,1,w,v);
        }
    }
    dij(pb[s]);//跑dij时记得时从b树结点开始跑
    for(int i=1;i<=n;i++)
    {
        if(dis[pb[i]]!=llinf)
            printf("%lld ",dis[pb[i]]);
        else
            printf("-1 ");
    }
    printf("\n");
}

花了两个多小时看题解才写出来 好难啊这些专题
有时间还是要自己重新写一下

猜你喜欢

转载自blog.csdn.net/daydreamer23333/article/details/107339520