luogu1261 服务器储存信息问题[最短路]
首先$O(n^2\log n)$暴力很好想,直接每个点出发跑一遍最短路,排$dis$统计一下即可。
考虑怎么优化。
发现$rank$很小,考虑从$rank$入手。
换一种统计方法,看每个点$x$如果作为别的点的兴趣点,可能产生多少贡献。
那么别的点$i$到他的最短距离设为$dis_i$,$i$到所有$rank_x+1$的点里面最短的距离是$f_{i,rank_x+1}$,那么肯定只有$dis_i<f_{i,rank_x+1}$的时候才产生一次贡献。所以我们可以先从大的$rank$的点开始枚举跑单源最短路,计算他对所有点的贡献。
那么这里设$f_{x,r}$表示点$x$和所有$rank$为$r$(或者$\ge r$,因为这个是单调的)的点里距离最近的是多远。统计总答案。
但是复杂度并没有变啊。。。`````
事实上,这个复杂度确实没有办法优化了,但是,观察题目字眼,会发现题目善意的告诉了我们总贡献$\le 30n$。也就是说,真正在跑最短路的时候有效的$dis_i<f_{i,rank_x+1}$点就那么多个,必须尽可能排除没有贡献的点,只把有贡献的统计到。
发现,在以$s$跑dij的时候,若在$x$点去松弛了$y$,但是此时$dis_y\ge f_{y,rank_s+1}$,也就是$y$不可能对他感兴趣,那么即使$y$入堆后又去松弛了$z$,也不可能产生贡献并更新$f_{z,rank_s}$。因为:
$$
dis_z\ge dis_y+w_{y,z}\ge f_{y,rank_s+1}+w_{y,z}\ge f_{z,rank_z+1}
$$
通过这个连续不等关系,当松弛$y$的时候不满足产生贡献的判断式,就不要入堆了。这样,我们只有会产生贡献的点入堆,无贡献的点就不进了。虽然理论复杂度不变,但根据题目对于答案的保证,所以这题可通过。
反正我是没想到这个优化。。太神仙了。。
可能会注意到代码里面memset最多用了$30000$次,这个不会T吗。。不会,实际测试发现memset常数小的一批,n方过十万,可能是因为memset的字节填写比手动赋值快很多。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define dbg(x) cerr << #x << " = " << x <<endl 8 using namespace std; 9 typedef long long ll; 10 typedef double db; 11 typedef pair<int,int> pii; 12 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 13 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 14 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 15 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 16 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 17 template<typename T>inline T read(T&x){ 18 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 19 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 20 } 21 const int N=30000+2,M=150000+2; 22 struct thxorz{int to,nxt,w;}G[M<<1]; 23 int Head[N],tot; 24 vector<int>rk[11]; 25 int n,m,ans; 26 inline void Addedge(int x,int y,int z){ 27 G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot,G[tot].w=z; 28 G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot,G[tot].w=z; 29 } 30 #define y G[j].to 31 int f[N][11],dis[N]; 32 priority_queue<pii,vector<pii>,greater<pii> >q; 33 inline void dij(int s,int r){ 34 memset(dis,0x3f,sizeof dis);q.push(make_pair(dis[s]=0,s)); 35 while(!q.empty()){ 36 int x=q.top().second,d=q.top().first;q.pop(); 37 if(dis[x]^d)continue; 38 ++ans,MIN(f[x][r],d); 39 for(register int j=Head[x];j;j=G[j].nxt) 40 if(MIN(dis[y],d+G[j].w)&&dis[y]<f[y][r+1])q.push(make_pair(dis[y],y)); 41 } 42 } 43 #undef y 44 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 45 read(n),read(m); 46 for(register int i=1,x;i<=n;++i)read(x),rk[x].push_back(i); 47 for(register int i=1,x,y,z;i<=m;++i)read(x),read(y),read(z),Addedge(x,y,z); 48 memset(f,0x3f,sizeof f); 49 for(register int i=10;i;--i){ 50 for(register int j=0;j<rk[i].size();++j)dij(rk[i][j],i); 51 for(register int j=0;j<rk[i].size();++j)f[rk[i][j]][i]=0; 52 for(register int j=1;j<=n;++j)f[j][i-1]=f[j][i]; 53 } 54 return printf("%d\n",ans),0; 55 }
反思:如果题目对于答案有相关的保证,优化可以从排除不可能,尽量只统计到答案的角度来进行。例如本题要排除多余策略,必须发现第一次出现的不可能策略,以后都不可能推出可能的出来这个性质,而这个要靠猜想是不是由于只能统计答案那么次所以一旦有不产生贡献的冗枝立刻剪掉。最短路上点过多,尝试去掉不可能的。