Description
给定 (n) 个点,(m) 条边。每个点 (i) 有权值 (r(i))。
定义点 (v) 对点 (w) 感兴趣,当且仅当不存在任何一个点 (u) 满足 (r(u)>r(w)) 且 (delta(v,u)le delta(v,w))。
其中 (delta(i,j)) 表示点 (i) 到点 (j) 的最短路。
要求求出所有点感兴趣的点的数量之和。
Solution
朴素的想法是暴力跑最短路统计答案,在 (nle 30000) 的范围下效率不言而喻。
通过简单分析可以发现,权值的大小对于是否感兴趣起到了很大的作用。
具体来说,当一个点权值更大,距离不会更远时,才会对答案有贡献。
或者说,若点 (a) 对点 (b) 感兴趣,那么点 (a) 要对权值小于 (b) 的点 (c) 感兴趣的条件是 (delta(a,c)<delta(a,b))
然后,因为跑 (n) 遍最短路效率低下的根本原因是更新次数太多,所以我们可以利用上面的几个条件来减少更新次数,具体的,就是通过怎么增加限制条件减少一个更新过的点出队后再入队的次数。
发现数据范围 (1le r(i)le 10) 非常小,所以我们可以从权值入手。
我们设 (d_{v,k}) 表示离点 (k) 最近的权值大于等于 (v) 的点到 (k) 的距离。
那么点 (u) 对点 (v) 感兴趣的条件是 (d_{r(v)+1,u}>delta(u,v))。
根据 SPFA 的松弛操作,假设我们从点 (a) 扩展到点 (b) 且 (b) 不对 (a) 感兴趣时,从点 (b) 又扩展到了点 (c),我们会有如下条件:
根据这个条件可得:
所以由上面的分析可以推断,当点 (a) 扩展出的点 (b) 对 (a) 不感兴趣时,点 (b) 扩展出的点也都对 (a) 不敢兴趣,即对答案无贡献。
根据这个条件,我们在遇到不感兴趣的点后停止扩展,可以极大的提高效率。
至于 (d) 的求法,先跑最短路处理出每种权值下的部分点,对于无法处理的点,继承上一个点的即可。
Code
void spfa1(int x){
memset(d[x],INF,sizeof d[x]);
memset(vis,false,sizeof vis);
queue<int> q;
for(int i=1;i<=n;i++){
if(val[i]^x) continue;
d[x][i]=0;q.push(i);
}
while(!q.empty()){
int u=q.front();
q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(d[x][u]+e[i].dis<d[x][to]){
d[x][to]=d[x][u]+e[i].dis;
if(!vis[to])vis[to]=1,q.push(to);
}
}
}
}
void spfa2(int x){
memset(Dis,INF,sizeof Dis);
memset(vis,false,sizeof vis);
memset(flag,false,sizeof flag);
queue<int> q;Dis[x]=0;q.push(x);
while(!q.empty()){
int u=q.front();
q.pop();vis[u]=0;
if(!flag[u]) flag[u]=1,Ans++;
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(Dis[to]>Dis[u]+e[i].dis){
Dis[to]=Dis[u]+e[i].dis;
if(!vis[to]&&Dis[to]<d[val[x]+1][to])
vis[to]=1,q.push(to);
}
}
}
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)val[i]=read();
for(int i=1,fr,to,dis;i<=m;i++){
fr=read();to=read();dis=read();
add(fr,to,dis);add(to,fr,dis);
}
for(int i=1;i<=10;i++) spfa1(i);
for(int i=9;i>=1;i--)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i+1][j]);
for(int i=1;i<=n;i++) spfa2(i);
printf("%d
",Ans);
return 0;
}