又是一道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");
}
花了两个多小时看题解才写出来 好难啊这些专题
有时间还是要自己重新写一下