线段树优化建图小结(其实是三个有水有不水的题……)
我们发现在有些数据范围内是不允许我们把图上的所有边建出来的
这种情况同时还可能附加点可以转到序列上啥的
然后我们对编号为下标建线段树
线段树上的每个节点的 \(l\) 和 \(r\) 就是把 \(l\rightarrow r\) 中的所有点缩到一个点表示了
然后这里我们完善一下:
把每个点拆一下,成一个入点,一个出点
入点用一棵线段树维护,出点用一棵
我们在入点从上往下建边权为 \(0\) 的边
含义:到了表示区间的点肯定可以到子区间
出点从下往上建 \(0\) 边
含义类似
入点线段树的每个点向出点线段树的对应位置的点建 \(0\) 边 ,从这里进必然可以出
如果出现区间向点连边:
建立一个虚拟点,把出区间拆成 \(\log\) 份,连向虚拟点,边权为零,把入区间也拆成 \(\log\) 份 ,边权为\(val\)
至于区间向区间连……待填(毕竟\(NOI2019\) 也只是点向矩形连……)
最短路一样跑
板子题: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;}
其它的题目:
\(1.\) \(POI2015 PUS\)
直接建图跑拓扑最长路是不可取的
我们考虑线段树优化建图
把虚拟点向给的点连边,\(w=0\)
然后把剩下的区间向点连,\(w=1\)
考虑到每个点是有 \(id\),那么我们发现在拓扑的过程中线段树上的叶子显然会入队
所以正确性是有保证的
最后直接拓扑最长路即可,感觉没啥细节
\(Code:\)
#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=3e5+10;
int head[N*40],cnt,tot=1,in[N*40];
struct node{
int to,dis,nxt;
}e[N*40];
inline void add(int u,int v,int w)
{
e[++cnt].dis=w; e[cnt].nxt=head[u]; e[cnt].to=v; in[v]++;
return head[u]=cnt,void();
}
int n,s,m,id[N*40];
int ls[N*40],rs[N*40];
#define ls(x) ls[x]
#define rs(x) rs[x]
inline void build(int p,int l,int r)
{
if(l==r) return id[l]=p,void();
int mid=(l+r)>>1;
ls(p)=++tot; add(ls(p),p,0);
rs(p)=++tot; add(rs(p),p,0);
build(ls(p),l,mid);
build(rs(p),mid+1,r);
return ;
}
inline void update(int p,int l,int r,int st,int ed,int x)
{
if(r<l) return ;
if(l<=st&&ed<=r)
{
if(x==p) return ;
return add(p,x,1);
} int mid=(st+ed)>>1;
if(l<=mid) update(ls(p),l,r,st,mid,x);
if(r>mid) update(rs(p),l,r,mid+1,ed,x);
return ;
}
int dis[N<<5],vis[N<<5],a[N<<5];
inline void work()
{
queue<int> q; memset(dis,0xcf,sizeof(dis));
for(int i=1;i<=tot;++i)
{
if(!in[i])
{
if(a[i]) dis[i]=a[i]; else dis[i]=1;
q.push(i); vis[i]=1;
}
}
while(!q.empty())
{
int fr=q.front(); q.pop(); vis[fr]=1;
for(int i=head[fr];i;i=e[i].nxt)
{
int y=e[i].to; in[y]--;
dis[y]=max(dis[y],dis[fr]+e[i].dis);
if(!in[y])
{
if(a[y])
if(a[y]<dis[y]) return puts("NIE"),void();
else dis[y]=a[y];
q.push(y);
}
}
}
for(int i=1;i<=tot;++i)
{
if(!vis[i]) puts("NIE"),exit(0);
if(dis[i]>1e9) puts("NIE"),exit(0);
}
puts("TAK");
for(int i=1;i<=n;++i) printf("%lld ",dis[id[i]]);
return puts(""),void();
}
int pos,val;
signed main()
{
n=read(); s=read(); m=read();
build(1,1,n);
while(s--) pos=read(),val=read(),a[id[pos]]=val;
for(int i=1;i<=m;++i)
{
int l=read(),x,r=read(),las=l-1,k=read();
++tot;
while(k--)
{
x=read();
update(1,las+1,x-1,1,n,tot);
add(tot,id[x],0);
las=x;
} update(1,las+1,r,1,n,tot);
}
work();
return 0;
}
}
signed main(){return yspm::main();}
\(2.\) [SNOI2017]炸弹
这题算是比较神了
首先可以处理出来每个点的控制范围,然后我们连边(点向区间)
注意这里是有向边,所以是父亲到儿子连
然后我们发现可以 \(tarjan\) 出来强连通的炸弹
正确性还是可以理解的
依题意:我们缩点跑完之后接着 \(dfs\) 一发,求出来每个炸弹爆炸后影响的最左端和最右端的点
最后求答案即可
全是板子……直接打就行了
#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=4e6+10,mod=1e9+7;
int x[N],r[N],st,ed,n;
struct node{
int l,r;
#define l(p) t[p].l
#define r(p) t[p].r
}t[N];
int maxx,id[N];
vector<int> vec[N],g[N];
inline void build(int p,int l,int r)
{
l(p)=l; r(p)=r;
maxx=max(maxx,p);
if(l==r) return id[l]=p,void();
int mid=(l+r)>>1;
build(p<<1,l,mid); build(p<<1|1,mid+1,r);
g[p].push_back(p<<1); g[p].push_back(p<<1|1);
return ;
}
inline void work(int p,int u,int v,int l,int r,int x)
{
if(l<=u&&v<=r)
{
if(p!=x) g[x].push_back(p);
return ;
}
int mid=(u+v)>>1;
if(l<=mid) work(p<<1,u,mid,l,r,x);
if(r>mid) work(p<<1|1,mid+1,v,l,r,x);
return ;
}
int s[N],top;
int dfn[N],low[N],vis[N],tim,bel[N],tl[N],tr[N],cnt,ans;
inline void tarjan(int x)
{
dfn[x]=low[x]=++tim; s[++top]=x; vis[x]=1;
int siz=g[x].size();
for(int i=0;i<siz;++i)
{
int t=g[x][i];
if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]);
else if(vis[t]) low[x]=min(low[x],dfn[t]);
}
if(low[x]==dfn[x])
{
cnt++;
do{
vis[s[top]]=0;
tl[cnt]=min(tl[cnt],l(s[top]));
tr[cnt]=max(tr[cnt],r(s[top]));
bel[s[top]]=cnt;
top--;
} while(s[top+1]!=x);
}
return ;
}
inline void dfs(int x)
{
vis[x]=1;
int siz=vec[x].size();
for(int i=0;i<siz;++i)
{
int t=vec[x][i];
if(!vis[t]) dfs(t);
tl[x]=min(tl[x],tl[t]); tr[x]=max(tr[x],tr[t]);
}
return ;
}
signed main()
{
memset(tl,0x3f,sizeof(tl));
memset(tr,-0x3f,sizeof(tr));
n=read(); build(1,1,n);
for(int i=1;i<=n;++i) x[i]=read(),r[i]=read();
x[n+1]=0x3f3f3f3f3f3f3f;
for(int i=1;i<=n;++i)
{
if(!r[i]) continue;
st=lower_bound(x+1,x+n+1,x[i]-r[i])-x;
ed=upper_bound(x+1,x+n+1,x[i]+r[i])-x-1;
if(x[ed]>x[i]+r[i]) ed--;
work(1,1,n,st,ed,id[i]);
l(id[i])=st; r(id[i])=ed;
}
tarjan(1);
for(int i=1;i<=maxx;++i)
{
int siz=g[i].size();
for(int j=0;j<siz;++j)
{
if(bel[i]!=bel[g[i][j]])
{
vec[bel[i]].push_back(bel[g[i][j]]);
}
}
}
for(int i=1;i<=cnt;++i)
{
sort(vec[i].begin(),vec[i].end());
unique(vec[i].begin(),vec[i].end());
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=cnt;++i) if(!vis[i]) dfs(i);
for(int i=1;i<=n;++i)
{
ans+=(tr[bel[id[i]]]-tl[bel[id[i]]]+1)*i%mod; ans%=mod;
} printf("%lld\n",ans);
return 0;
}
}
signed main(){return yspm::main();}