「JOISC 2022」复兴计划
题目
点这里看题目。
给定一张 \(N\) 个点 \(M\) 条边的无向连通无自环图。一条边除其连接的端点外,还有参数 \(w\)。
进行 \(Q\) 次询问,每次询问给定 \(x\),回答一条边的边权为 \(|x-w|\) 时最小生成树的代价之和。
\(100\%\) 的数据满足:\(1\le N\le 500, 1\le M\le 10^5,1\le Q\le 10^6\)。
分析
注意!本题并不是考虑凸包!不要看到线性贡献看到 \(\min\) or \(\max\) 就开始往这方面思考!
考虑边权 \(|x-W|\) 对于 Kruskal 算法带来的影响,很显然它会导致边的顺序改变。
为了避免边权相等,此处定义边的严格的序:首先比较边权,其次比较编号。此后,先定性分析:
-
如果一条原始边权为 \(W\) 的边 \(e\) 可以出现在 \(x=x_0\) 时的最小生成树中,那么它一定也会出现在 \(x=W\) 时的最小生成树中。
因为相对而言,这条边排名一定不会变大。
-
如果边 \(e\) 会出现在 \(x=W\) 时的最小生成树中,则使得它在最小生成树中的 \(x\) 必然构成一段区间。
这是因为,\(x\) 从 \(W\) 出发向一个方向不断移动时,若有另一条原始边权为 \(W'\) 的边,则 \(|x-W|-|x-W'|\) 不会变大。所以排序后的序列中这条边只会不断后移。
我们只需要找出这个“区间”即可。不妨先考虑左边界。注意到 \(x<W\) 时,一条原始边权为 \(W'(W'>W)\) 的边一定排在 \(e\) 之后,所以只用考虑 \(W'<W\) 的边。
对于函数 \(y_1=|x-a|\) 和 \(y_2=|x-b|\)(\(a<b\))来说,\(y_1,y_2\) 的大小关系在 \(\frac{a+b}{2}\) 处分界。在这个情景中,\(b=W\),而 \(a=W'\)。考虑 \(x=0\) 时的排序情况,其中 \(e\) 居于第 \(p\) 位,则任意 \(x\) 时排序后,居于 \(e\) 之前的边必然是 \(x=0\) 序列中以 \(p-1\) 结尾的一段后缀(可为空后缀)。所以,我们相当于需要找到最大的 \(q\),使得“将 \(x=0\) 序列中 \([q,p-1]\) 位置的边加入图中后,\(e\) 的两个端点已经连通”。该问题可以通过扫描线+维护最大标号生成树解决。
所以,暴力计算可以得到 \(O(MN+Q)\) 的算法,而使用 LCT 维护则可得到 \(O(N+M\log N+Q)\) 的算法。
代码
#include <bits/stdc++.h>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const int MAXN = 505, MAXM = 1e5 + 5, MAXQ = 1e6 + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
struct Edge {
int u, v, w, id;
};
Edge edg[MAXM];
int lef[MAXM], rig[MAXM], iniWei[MAXM];
std :: vector<std :: pair<int, int> > grph[MAXN];
LL coe1[MAXQ], coe2[MAXQ];
int arg[MAXQ];
int N, M, Q;
inline bool operator < ( const Edge &e1, const Edge &e2 ) {
return e1.w == e2.w ? e1.id < e2.id : e1.w < e2.w;
}
inline void AddEdge( const int &u, const int &v, const int &id ) {
grph[u].emplace_back( v, id );
grph[v].emplace_back( u, id );
}
inline void DelEdge( const int &u, const int &v, const int &id ) {
grph[u].erase( std :: find( grph[u].begin(), grph[u].end(), std :: make_pair( v, id ) ) );
grph[v].erase( std :: find( grph[v].begin(), grph[v].end(), std :: make_pair( u, id ) ) );
}
int FindEdge( const int &u, const int &fa, const int &tar ) {
int ret = 0;
if( u == tar ) ret = 1e9;
else {
for( const auto &e : grph[u] )
if( e.first ^ fa ) {
int tmp = FindEdge( e.first, u, tar );
if( tmp ) ret = std :: min( tmp, e.second );
}
}
return ret;
}
int main() {
Read( N ), Read( M );
rep( i, 1, M ) {
Read( edg[i].u ), Read( edg[i].v ), Read( edg[i].w );
iniWei[i] = edg[i].w, edg[i].id = i;
}
Read( Q );
rep( i, 1, Q ) Read( arg[i] );
std :: sort( edg + 1, edg + 1 + M );
rep( i, 1, M ) {
int tmp = FindEdge( edg[i].u, 0, edg[i].v );
if( tmp ) {
DelEdge( edg[tmp].u, edg[tmp].v, tmp );
lef[edg[i].id] = 1 + ( edg[tmp].w + edg[i].w ) / 2;
} else lef[edg[i].id] = 0;
AddEdge( edg[i].u, edg[i].v, i );
}
rep( i, 1, N ) grph[i].clear();
per( i, M, 1 ) {
int tmp = - FindEdge( edg[i].u, 0, edg[i].v );
if( tmp ) {
DelEdge( edg[tmp].u, edg[tmp].v, - tmp );
rig[edg[i].id] = ( edg[tmp].w + edg[i].w ) / 2;
} else rig[edg[i].id] = arg[Q] + 1;
AddEdge( edg[i].u, edg[i].v, - i );
}
rep( i, 1, M ) {
int l = std :: lower_bound( arg + 1, arg + 1 + Q, lef[i] ) - arg,
r = std :: upper_bound( arg + 1, arg + 1 + Q, rig[i] ) - arg - 1;
if( l <= r ) {
int p = std :: lower_bound( arg + 1, arg + 1 + Q, iniWei[i] ) - arg;
if( l < p ) {
coe1[l] --, coe2[l] += iniWei[i];
coe1[p] ++, coe2[p] -= iniWei[i];
}
if( p <= r ) {
coe1[p] ++, coe2[p] -= iniWei[i];
coe1[r + 1] --, coe2[r + 1] += iniWei[i];
}
}
}
rep( i, 1, Q ) {
coe1[i] += coe1[i - 1];
coe2[i] += coe2[i - 1];
Write( arg[i] * coe1[i] + coe2[i] ), putchar( '\n' );
}
return 0;
}