线段树优化建图
在有些数据范围内是不允许我们把图上的所有边建出来的
然后我们对编号为下标建线段树
线段树上的每个节点的 (l) 和 (r) 就是把 (l ightarrow r) 中的所有点缩到一个点表示了
然后这里我们完善一下:
把每个点拆一下,成一个入点,一个出点,分别用两个线段树维护
我们在入点从上往下建边权为 (0) 的边
含义:到了表示区间的点肯定可以到子区间
出点从下往上建 (0) 边
含义类似
入点线段树的每个点向出点线段树的对应位置的点建 (0) 边 ,从这里进必然可以出
如果出现区间向点连边:
建立一个虚拟点,把出区间拆成 (log) 份,连向虚拟点,边权为零,把入区间也拆成 (log) 份 ,边权为(val)
至于区间向区间连……
这里就直接建立两个虚拟点,每个虚拟点和区间拆成的 (log) 个小区间连边即可
最短路一样跑
例题
CF786B
模板题,那么贴一个代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=1e5+10;
struct node{int to,dis,next;}e[N<<5];
int n,m,cnt,head[N<<2],q,s;
inline void add(int u,int v,int w)
{
e[++cnt].dis=w; e[cnt].next=head[u]; e[cnt].to=v;
head[u]=cnt; return ;
}
int res[N<<2];
inline void spfa(int s)
{
queue<int> q; q.push(s); memset(res,0x3f,sizeof(res));res[s]=0;
while(!q.empty())
{
int fr=q.front(); q.pop();
for(int i=head[fr];i;i=e[i].next)
{
int t=e[i].to,dist=e[i].dis+res[fr];
if(dist>=res[t]) continue;
res[t]=dist; q.push(t);
}
}
return ;
}
int ls[N<<2],rs[N<<2],rt1,rt2,tot,L,R;
inline void build1(int &p,int l,int r)
{
if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
build1(ls[p],l,mid); build1(rs[p],mid+1,r); add(p,ls[p],0); add(p,rs[p],0);
return ;
}
inline void build2(int &p,int l,int r)
{
if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
build2(ls[p],l,mid); build2(rs[p],mid+1,r); add(ls[p],p,0); add(rs[p],p,0);
return ;
}
inline void update(int p,int l,int r,int u,int w,int type)
{
if(L<=l&&r<=R)
{
type==2? add(u,p,w):add(p,u,w);
return ;
}int mid=(l+r)>>1;
if(L<=mid) update(ls[p],l,mid,u,w,type);
if(R>mid) update(rs[p],mid+1,r,u,w,type);
return ;
}
int opt,u,v,w;
signed main()
{
n=read(); q=read(); s=read(); tot=n; build1(rt1,1,n); build2(rt2,1,n);
while(q--)
{
opt=read();
if(opt==1){u=read(); v=read(); w=read(); add(u,v,w);}
else
{
u=read(); L=read(); R=read(); w=read();
update(opt==2?rt1:rt2,1,n,u,w,opt);
}
}
spfa(s); for(int i=1;i<=n;++i) printf("%lld ",res[i]==0x3f3f3f3f3f3f3f3f? -1:res[i]); puts("");
return 0;
}
}
signed main(){yspm::main(); return 0;}
( m{POI2015 PUS})
直接建图跑拓扑最长路是不可取的
我们考虑线段树优化建图
把虚拟点向给的点连边,(w=0)
然后把剩下的区间向点连,(w=1)
考虑到每个点是有 (id),那么我们发现在拓扑的过程中线段树上的叶子显然会入队
所以正确性是有保证的
最后直接拓扑最长路即可,感觉没啥细节
[SNOI2017]炸弹
首先可以处理出来每个点的控制范围,然后我们连边(点向区间)
注意这里是有向边,所以是父亲到儿子连
然后我们发现可以 (tarjan) 出来强连通的炸弹
正确性还是可以理解的
依题意:我们缩点跑完之后接着 (dfs) 一发,求出来每个炸弹爆炸后影响的最左端和最右端的点
最后求答案即可
全是板子……直接打就行了