题解 P3238 【[HNOI2014]道路堵塞】
出完题后快三个月然后发现自己出重题了我的题目
题意简述
给定有向正权图,多次询问删除
题目分析
首先观察到一个性质:
答案必定是任取一条非最短路径树边,从
证明:由于只删除一条树边,则只需要使用一条非树边翻越一个点;设其为点
我们先分别预处理出来以
设边为
void getd(int u,int from){
top[u]=from;
for(foredge(i))
if(!dep[v]&&edges[i].in)
dep[v]=dep[u]+1,getd(v,onlist[v]?v:from);
}
那么对于每条非树边所产生的贡献,可以用线段树来维护。
复杂度分析
使用dijkstra求最短路树时复杂度为
代码
#include <bits/stdc++.h>
#define F(i,a,b) for(ll i=a;i<=b;i++)
#define ll long long
#define UF(i,a,b) for(ll i=a;i>=b;i--)
#define Pii pair<int,int>
#define lc pos<<1
#define rc pos<<1|1
#define foredge(i) int i=head[u],v=edges[i].to;i;i=edges[i].nxt,v=edges[i].to
using namespace std;
const ll N=100005,M=200005;
ll read(){
ll x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x;
}
void write(ll x){
if(x>9) write(x/10);
putchar(x%10+'0');
}
void write(ll x,char c){
if(x<0) putchar('-'),write(-x);
else write(x);
putchar(c);
}
int onlst[N];
struct Graph{
struct Edge{
int to,nxt,w,in;
Edge(int a=0,int b=0,int c=0){
nxt=a,to=b,w=c;
}
}edges[N<<1];
int epos,head[N];
void add(int u,int v,int w=0){
edges[++epos]=Edge(head[u],v,w);
head[u]=epos;
}
struct Pos{
int p,w,from,d;
Pos(int a=0,int b=0,int c=0){
p=a,w=b,from=c;
}
bool operator<(const Pos &b)const{
return w>b.w;
}
};
int dis[N],in[N],dep[N],top[N];
void dij(int s){
memset(dis,0x3f,sizeof(dis));
memset(in,0,sizeof(in));
dis[s]=0;
priority_queue<Pos> q;
q.push(Pos(s,0));
while(!q.empty()){
Pos nw=q.top();
q.pop();
int u=nw.p;
if(in[u]) continue;
in[u]=1, edges[nw.from].in=1;//最短路径树边
for(foredge(i)){
int tow=nw.w+edges[i].w;
if(dis[v]>tow){
dis[v]=tow;
q.push(Pos(v,tow,i));
}
}
}
}
void getd(int u,int from){//处理每个点所经过的最值链上点
top[u]=from;
for(foredge(i)) if(!dep[v]&&edges[i].in) dep[v]=dep[u]+1,getd(v,onlst[v]?v:from);
}
}g1,g2;
struct SegmentTree{//线段树维护最小值
int mn[N<<2],ltag[N<<2];
void init(){
memset(mn,0x3f,sizeof(mn));
memset(ltag,0x3f,sizeof(ltag));
}
void up(int pos){
mn[pos]=min(mn[lc],mn[rc]);
}
void down(int pos){
mn[lc]=min(mn[lc],ltag[pos]),mn[rc]=min(mn[rc],ltag[pos]);
ltag[lc]=min(ltag[lc],ltag[pos]),ltag[rc]=min(ltag[rc],ltag[pos]);
ltag[pos]=0x3f3f3f3f;
}
void modify(int pos,int l,int r,int tl,int tr,int k){
if(l>=tl&&r<=tr){
mn[pos]=min(mn[pos],k), ltag[pos]=min(ltag[pos],k);
return;
}
int mid=l+r>>1;
down(pos);
if(mid>=tl) modify(lc,l,mid,tl,tr,k);
if(mid<tr) modify(rc,mid+1,r,tl,tr,k);
up(pos);
}
int query(int pos,int l,int r,int tl,int tr){
if(l>=tl&&r<=tr) return mn[pos];
down(pos);
int mid=l+r>>1,ans=0x3f3f3f3f;
if(mid>=tl) ans=min(ans,query(lc,l,mid,tl,tr));
if(mid<tr) ans=min(ans,query(rc,mid+1,r,tl,tr));
return ans;
}
}seg;
int n,m,l;
int q[M];
int main(){//必然是1->x1的最短路+单独的边x1->x2+n->x2的最短路
n=read(),m=read(),l=read();
F(i,1,m){
int u=read(),v=read(),w=read();
g1.add(u,v,w),g2.add(v,u,w);
}
F(i,1,l) q[i]=read(),g1.edges[q[i]].in=1;
F(u,1,n){
for(int i=g1.head[u];i;i=g1.edges[i].nxt){
int v=g1.edges[i].to;
if(g1.edges[i].in){
g1.edges[i].in=0;
onlst[u]=onlst[v]=1;
}
}
}
g1.dij(1),g2.dij(n);
g1.dep[1]=1,g1.getd(1,1);
seg.init();
//n->x2最短路一定是x2->x3与x3->n,后半部分与给定的1->n链重叠,则预处理出反向图所有点到n经过的链上最高点
g2.dep[n]=1,g2.getd(n,n);
F(u,1,n){
for(int i=g1.head[u];i;i=g1.edges[i].nxt){
int v=g1.edges[i].to;
int l=g1.dep[g1.top[u]]+1,r=g1.dep[g2.top[v]];//答案的贡献区间
if(g1.edges[i].in) continue;
seg.modify(1,1,n,l,r,g1.dis[u]+g1.edges[i].w+g2.dis[v]);//答案
}
}
F(i,1,l){
int ans=seg.query(1,1,n,g1.dep[g1.edges[q[i]].to],g1.dep[g1.edges[q[i]].to]);//每条边下放到目标节点
if(ans<1e9+10) write(ans,'\n');
else write(-1,'\n');//无解
}
return 0;
}