前言
这不是线段树优化建图板题吗?哦,在并查集板块啊,那没事了。
想破头都想不到怎么用并查集,看网上题解发现都是一笔带过,那我写一写吧。
题目
题目大意:
\(T\) 组数据,每次给 \(n\) 个点,相邻两个点的距离为 \(1\),给出 \(l_i,r_i,c_i\) 表示从节点 \(i\) 出发可以到达于其距离为 \([l_i,r_i]\) 的点,花费为 \(c_i\)。
起点为 \(1\) 号点,问到每个点的最小花费,无法到达输出 \(-1\)。
\(1\le n\le 2\times 10^5;0\le l_i\le r_i\le n,1\le c_i\le 10^9.\)
讲解
有一个最朴素的想法是对将题中所有边全部连上,然后跑最短路算法,但我们无法接受大量的连边数量。
考虑优化,首先你需要十分了解 \(\tt dijkstra\) 算法,每个点一旦从小根堆中弹出,就不会再更新,在此基础上,我们可以用并查集 \(f_i\) 维护 \(i\) 后面最近的一个没被更新的点。
到这里你可能还没有明白这个并查集到底用来干什么,不过你先记住好了。
我们发现题目中难处理的是一个点会向很多其它点连边,我们不妨看作是一个点有很多点向其连边。
但是那些点连过来的边边权不一,很麻烦,这里有一个巧妙的做法:花费提前计算。
也就是说,对于起点 \(1\),它的最短路距离 \(dis_1\) 为 \(c_1\),而连到节点 \(i\) 的边权值为 \(c_i\)。
最后的答案即为 \(dis_i-c_i\)。
首先正确性显然,因为每个点的最短路距离都加上了 \(c\),相对大小不影响,仍然可以跑最短路。
其次,这样做的好处是跑最短路时你不需要知道来的那个点具体是哪一个,你只需要知道到的那个点即可转移。
这样一来,我们就可以将我们到过的点用并查集缩起来,具体来讲,我们将节点 \(i\) 连到它后面最近的一个没被最短路更新的点上,当我们试图到达节点 \(i\) 时,直接跳到后面那个点即可。这样做其实就是让可以用来更新其它点的点不再被当前试图更新其它点的点更新,从而保证时间复杂度。
也就是本文开头说的 \(\tt dijkstra\) 中一个节点一旦被弹出便不会被更新。
代码
//12252024832524
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 200005;
const LL INF = (1ll << 60);
int n;
int l[MAXN],r[MAXN],c[MAXN];
LL dis[MAXN];
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
struct node
{
int x;LL val;
node(){}
node(int x1,LL val1){
x = x1;
val = val1;
}
bool operator < (const node &px)const{
return val > px.val;
}
};
priority_queue<node> q;
int d[2] = {1,-1};
int f[MAXN];
int findSet(int x)
{
if(x ^ f[x]) f[x] = findSet(f[x]);
return f[x];
}
void unionSet(int u,int v)
{
u = findSet(u); v = findSet(v);
if(u ^ v) f[u] = v;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
for(int T = Read(); T ;-- T)
{
n = Read();
for(int i = 1;i <= n;++ i) l[i] = Read();
for(int i = 1;i <= n;++ i) r[i] = Read();
for(int i = 1;i <= n;++ i) c[i] = Read();
for(int i = 1;i <= n+1;++ i) dis[i] = INF,f[i] = i;
q.push(node(1,dis[1] = c[1]));
while(!q.empty())
{
node t = q.top(); q.pop();
for(int i = 0;i < 2;++ i)
{
int L = t.x + d[i] * l[t.x],R = t.x + d[i] * r[t.x];
if(L > R) swap(L,R);
L = Max(L,1); R = Min(R,n);
for(int j = L;j <= R;++ j)
{
j = findSet(j);
if(j > R) break;
if(dis[t.x] + c[j] < dis[j]) q.push(node(j,dis[j] = dis[t.x] + c[j]));
unionSet(j,j+1);
}
}
}
for(int i = 1;i <= n;++ i)
{
if(dis[i] == INF) Put(-1);
else Put(dis[i]-c[i]);
putchar(i < n ? ' ' : '\n');
}
}
return 0;
}
吐槽
网上其他题解(我看到的):用并查集优化维护处理过的节点。
我直接云里雾里。