最短路
直接应用
这道题居然想到了状态:\(f[i][j]\) 表示节点 \(i\) 经过距离比最短路多 \(j\) 的方案数,由于 \(k\) 特别小,所以第二维可以装下。转移也比较容易
关于这题零环的判断以及多次绕正环的处理比较麻烦,于是采用新科技——倒叙dfs遍历
因为从终点开始遍历可以减去很多超过 \(k\) 的枝,对于零环,每次遍历都用 \(bool\) 打标记,如果一个点在相同深度经过了两次,说明有零环直接退就可以了
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,maxm=4e5+5;
int n,m,dis[maxn],t,ans,mod,hd[maxn],hd1[maxn],cnt1,cnt,f[maxn][55],x,y,w,k;
bool vis1[maxn][55],vis[maxn],flag;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
struct Edge{
int nxt,to,val;
}edge[maxm],edge1[maxm];
void add(int u,int v,int w){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
edge[cnt].val=w;
hd[u]=cnt;
return ;
}
void add1(int u,int v,int w){
edge1[++cnt1].nxt=hd1[u];
edge1[cnt1].to=v;
edge1[cnt1].val=w;
hd1[u]=cnt;
return ;
}
struct Node{
int dis,id;
Node(){}
Node(int x,int y):dis(x),id(y){}
bool operator < (const Node & x)const{
return dis>x.dis;
}
};
priority_queue<Node>q;
void dij(){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[1]=0;
q.push(Node(0,1));
while(!q.empty()){
int u=q.top().id;
q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val){
dis[v]=dis[u]+edge[i].val;
q.push(Node(dis[v],v));
}
}
}
return ;
}
int dfs(int u,int deep){
if(deep<0||deep>k)return 0;
if(vis1[u][deep]){
vis1[u][deep]=false;
return -1;
}
if(~f[u][deep])return f[u][deep];
int sum=0;
vis1[u][deep]=true;
for(int i=hd1[u];i;i=edge1[i].nxt){
int v=edge1[i].to;
w=dfs(v,dis[u]+deep-dis[v]-edge1[i].val);
if(w==-1){
vis1[u][deep]=false;
return -1;
}
sum=(sum+w)%mod;
}
vis1[u][deep]=false;
if(u==1&&deep==0)sum=1;
f[u][deep]=sum;
return sum;
}
int main(){
t=read();
while(t--){
n=read();
m=read();
k=read();
mod=read();
ans=0;
cnt=cnt1=0;
memset(hd,0,sizeof hd);
memset(hd1,0,sizeof hd1);
for(int i=1;i<=m;i++){
x=read();
y=read();
w=read();
add(x,y,w);
add1(y,x,w);
}
dij();
// cout<<dis[n]<<endl;
memset(f,-1,sizeof f);
flag=false;
for(int i=0;i<=k;i++){
w=dfs(n,i);
if(w==-1){
flag=true;
break;
}
ans=(ans+w)%mod;
}
if(flag)puts("-1");
else cout<<ans<<endl;
}
return 0;
}
首先看清题——修改是暂时的,只对当前询问有效!!!
可以分类讨论:
- 不在最短路上,变大了:答案不变
- 不在最短路上,变小了:答案可能变成 \(dis(1,u)+dis(v,n)+val\)
- 在最短路上,变小了:答案可能变成 \(dis(1,n)-edge[i].val+val\)
- 在最短路上,变大了:这时是最棘手的情况,可能最短路变成了其他路径,然而并不一定是原图上的次短路,因为次短路可能也用到了这段路径
于是引入新科技——线段树来求解
首先预处理出原图的一条最短路径,并记录上面所有的边、点
线段树维护不经过路径上区间 \([l,r]\) 的点的最短路长度
至于维护,需要预处理另外一个东西:
像这张图一样,\(l[u]\) 表示从1到 \(u\) 这个点经过最短路上最后一条边是哪一个,\(r[v]\) 同理
这样对于每条边,更新其端点限制的一段区间即可,即 \(l[u]+1,r[v]-1\)
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+5;
const int maxm=4e5+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,qq,hd[maxn],cnt,x,y,w,ans,dis1[maxn],dis[maxn],id[maxn],pre[maxn],l[maxn],r[maxn],tot;
bool vis[maxn],one[maxn],onp[maxn];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
struct Edge{
int nxt,to,from,val;
}edge[maxm];
void add(int u,int v,int w){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
edge[cnt].val=w;
edge[cnt].from=u;
hd[u]=cnt;
return ;
}
struct Node{
int dis,id;
Node(){}
Node(int x,int y):dis(x),id(y){}
bool operator < (const Node & x)const{
return dis>x.dis;
}
};
priority_queue<Node>q;
void dij(int s,int op){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
q.push(Node(dis[s],s));
while(!q.empty()){
int u=q.top().id;
q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val){
if(!op)pre[v]=i;
if(op==1&&(!onp[v]))l[v]=l[u];
if(op==2&&(!onp[v]))r[v]=r[u];
dis[v]=dis[u]+edge[i].val;
q.push(Node(dis[v],v));
}
}
}
return ;
}
struct Seg{
int l,r,val;
}t[maxn*4];
void build(int p,int l,int r){
t[p].l=l;
t[p].r=r;
t[p].val=inf;
if(l==r){
// t[p].val=inf;
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
return ;
}
void spread(int p){
t[p<<1].val=min(t[p<<1].val,t[p].val);
t[p<<1|1].val=min(t[p<<1|1].val,t[p].val);
return ;
}
void change(int p,int l,int r,int w){
if(l>r)return ;
// cout<<t[p].l<<" "<<t[p].r<<" "<<l<<" "<<r<<endl;
if(t[p].l>=l&&t[p].r<=r){t[p].val=min(t[p].val,w);return ;}
spread(p);
int mid=t[p].l+t[p].r>>1;
if(l<=mid)change(p<<1,l,r,w);
if(r>mid)change(p<<1|1,l,r,w);
return ;
}
void find_path(){
for(int u=1;u;u=edge[pre[u]^1].to){
onp[u]=true;
one[pre[u]>>1]=true;
id[pre[u]>>1]=++tot;
l[u]=tot-1;
r[u]=tot;
}
tot--;
return ;
}
int ask(int p,int pos){
if(t[p].l==t[p].r&&t[p].l==pos)return t[p].val;
spread(p);
int mid=t[p].l+t[p].r>>1;
if(pos<=mid)return ask(p<<1,pos);
else return ask(p<<1|1,pos);
}
signed main(){
cnt=1;
n=read();
m=read();
qq=read();
for(int i=1;i<=m;i++){
x=read();
y=read();
w=read();
add(x,y,w);
add(y,x,w);
}
dij(n,0);
find_path();
// cout<<tot<<endl;
// cout<<"hhh";
dij(1,1);
memcpy(dis1,dis,sizeof dis1);
dij(n,2);
build(1,1,tot);
// cout<<"hhh";
for(int i=2;i<=cnt;i++){
if(!one[i>>1]){
int u=edge[i].from;
int v=edge[i].to;
change(1,l[u]+1,r[v]-1,dis1[u]+dis[v]+edge[i].val);
}
}
// for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
// cout<<endl;
// cout<<dis1[n]<<endl;
for(int i=1;i<=qq;i++){
x=read();
w=read();
int u=edge[x<<1].from;
int v=edge[x<<1].to;
// cout<<"hhh "<<u<<" "<<v<<" "<<one[x]<<endl;
ans=dis1[n];
if(!one[x]){
ans=min(ans,dis1[u]+dis[v]+w);
ans=min(ans,dis1[v]+dis[u]+w);
}
else{
ans=ans-edge[x<<1].val+w;
if(w>edge[x<<1].val){
ans=min(ans,ask(1,id[x]));
}
}
printf("%lld\n",ans);
}
return 0;
}