相关链接
Orz AHdoc!!!!!!!!!!!!!
这种神犇算法的关键在于真正利用了MST是一棵“树”的性质。也就是,它在求出MST后把它转化为有根树,然后, 按长度递增顺序对于图中每一条不在MST中的边(i, j),找到树中i、j的最近公共祖先(LCA),记为p=LCA(i, j)。这样, 树中i->p->j就是从i到j的路径。然后,依次扫描这条路径上的所有的边,将新边(i, j)的长度与路径上所有边的长度比较,找到长度差最小的(不过由于边(i, j)的长度一定不小于路径上所有边的长度,所以只要找到路径上最长边,则“删去这条最长边,加入边(i, j)”一定是所有加入的边为(i, j)的可行变换中代价最小的),取这个长度差最小的即可。不过最为神犇的一点是,这个算法在遍历完这条路径后, 会将路径上所有的点(p点除外)的父结点全部设为p,也就是相当于并查集的路径压缩!这虽然会改变树的形态,但任何两点的LCA都是不会变的,因此不会影响后面的边。
注意上面“按长度递增顺序”是重点,原因是“路径压缩”可能会改变某些点之间的路径,也就是将某些点之间的路径长度减小。但是,很容易发现, 被“压缩”的这些边必然是已经访问过的 ,也就是说这些边必然已经作为了前面的某条边(i, j),i到j路径上的边。对于这条边来说,可行变换中,新加入的边的长度应尽量小,因此,如果按长度递增顺序,则这些边在(i, j)之后肯定不会出现代价更小的可行变换,因此就可以将它们压缩,不会影响最优解。
复杂度分析:先不管求LCA的时间复杂度。设树中结点i的深度为h[i](h[root]=0)。对于树中的任意一个叶结点v,从root到v的路径的总长度(总边数)为h[v],因此,若某次要尝试的边(i, j)的某一端点(设为i)在从root到v的这条路径上,则p=LCA(i, j)一定也在这条路径上。这样,访问从i到p的路径上的总访问次数(也就是从i到p路径上的边数)为(h[i]-h[p])。在访问完成后,需要将从i到p路径上除p外的所有结点的父结点都设为p,也就是 从root到v的路径的总长度减少了(h[i]-h[p]-1)。因此,在尝试所有不在MST中的边的过程中,访问从root到v的最初路径上的边的总次数不会超过(h[v]+这些边的总数)(这里h[v]指初始的h[v])。因此可以得到: 访问树中所有边的总次数不会超过(最初所有叶结点深度之和+2*M),M为所有不在MST中边的总数!由于“最初所有叶结点深度之和”不会超过Nlog2N,因此总时间复杂度为O(Mlog2M+M+Nlog2N),其中O(Mlog2M)为用Kruskal求MST的时间,如果忽略这部分时间,则总时间复杂度为O(M+Nlog2N)。
其实这个算法的时间复杂度在忽略排序的情况下是线性的,即O(M+N),但本沙茶搞不懂怎么证明这一步。
下面是具体实现时的注意事项:
(1)将MST转化为有根树时,应用BFS,而不要用DFS,否则对于特殊数据可能爆栈;
(2)求LCA时,应用先让深度大的结点向上的方法(AHdoc神犇的方法),具体见下面的代码片段1;或者应用两者同时往上的方法(本沙茶的方法),具体见下面的代码片段2;否则,对于树是一条链,且每次访问都是访问最深的两个结点时,一次求LCA的时间复杂度可能升到O(N)。
【代码片段1】
【具体题目】Beijing2010 Tree(BZOJ1977)
这题要求严格次小生成树,因此在枚举的时候要注意,不能使可行变换的代价为0。
Orz AHdoc!!!!!!!!!!!!!
这种神犇算法的关键在于真正利用了MST是一棵“树”的性质。也就是,它在求出MST后把它转化为有根树,然后, 按长度递增顺序对于图中每一条不在MST中的边(i, j),找到树中i、j的最近公共祖先(LCA),记为p=LCA(i, j)。这样, 树中i->p->j就是从i到j的路径。然后,依次扫描这条路径上的所有的边,将新边(i, j)的长度与路径上所有边的长度比较,找到长度差最小的(不过由于边(i, j)的长度一定不小于路径上所有边的长度,所以只要找到路径上最长边,则“删去这条最长边,加入边(i, j)”一定是所有加入的边为(i, j)的可行变换中代价最小的),取这个长度差最小的即可。不过最为神犇的一点是,这个算法在遍历完这条路径后, 会将路径上所有的点(p点除外)的父结点全部设为p,也就是相当于并查集的路径压缩!这虽然会改变树的形态,但任何两点的LCA都是不会变的,因此不会影响后面的边。
注意上面“按长度递增顺序”是重点,原因是“路径压缩”可能会改变某些点之间的路径,也就是将某些点之间的路径长度减小。但是,很容易发现, 被“压缩”的这些边必然是已经访问过的 ,也就是说这些边必然已经作为了前面的某条边(i, j),i到j路径上的边。对于这条边来说,可行变换中,新加入的边的长度应尽量小,因此,如果按长度递增顺序,则这些边在(i, j)之后肯定不会出现代价更小的可行变换,因此就可以将它们压缩,不会影响最优解。
复杂度分析:先不管求LCA的时间复杂度。设树中结点i的深度为h[i](h[root]=0)。对于树中的任意一个叶结点v,从root到v的路径的总长度(总边数)为h[v],因此,若某次要尝试的边(i, j)的某一端点(设为i)在从root到v的这条路径上,则p=LCA(i, j)一定也在这条路径上。这样,访问从i到p的路径上的总访问次数(也就是从i到p路径上的边数)为(h[i]-h[p])。在访问完成后,需要将从i到p路径上除p外的所有结点的父结点都设为p,也就是 从root到v的路径的总长度减少了(h[i]-h[p]-1)。因此,在尝试所有不在MST中的边的过程中,访问从root到v的最初路径上的边的总次数不会超过(h[v]+这些边的总数)(这里h[v]指初始的h[v])。因此可以得到: 访问树中所有边的总次数不会超过(最初所有叶结点深度之和+2*M),M为所有不在MST中边的总数!由于“最初所有叶结点深度之和”不会超过Nlog2N,因此总时间复杂度为O(Mlog2M+M+Nlog2N),其中O(Mlog2M)为用Kruskal求MST的时间,如果忽略这部分时间,则总时间复杂度为O(M+Nlog2N)。
其实这个算法的时间复杂度在忽略排序的情况下是线性的,即O(M+N),但本沙茶搞不懂怎么证明这一步。
下面是具体实现时的注意事项:
(1)将MST转化为有根树时,应用BFS,而不要用DFS,否则对于特殊数据可能爆栈;
(2)求LCA时,应用先让深度大的结点向上的方法(AHdoc神犇的方法),具体见下面的代码片段1;或者应用两者同时往上的方法(本沙茶的方法),具体见下面的代码片段2;否则,对于树是一条链,且每次访问都是访问最深的两个结点时,一次求LCA的时间复杂度可能升到O(N)。
【代码片段1】
int
lca(
int
a,
int
b)
{
for (;;)
{
if (a == b) return b;
if (h[a] >= h[b]) a = Fa[a]; else b = Fa[b];
}
}
【代码片段2】
{
for (;;)
{
if (a == b) return b;
if (h[a] >= h[b]) a = Fa[a]; else b = Fa[b];
}
}
int
LCA(
int
x,
int
y)
{
while (x && y) {
if (fl[x] == _s) return x; else fl[x] = _s;
if (fl[y] == _s) return y; else fl[y] = _s;
x = pr[x]; y = pr[y];
}
if (x) while ( 1 ) if (fl[x] == _s) return x; else x = pr[x]; else while ( 1 ) if (fl[y] == _s) return y; else y = pr[y];
}
{
while (x && y) {
if (fl[x] == _s) return x; else fl[x] = _s;
if (fl[y] == _s) return y; else fl[y] = _s;
x = pr[x]; y = pr[y];
}
if (x) while ( 1 ) if (fl[x] == _s) return x; else x = pr[x]; else while ( 1 ) if (fl[y] == _s) return y; else y = pr[y];
}
【具体题目】Beijing2010 Tree(BZOJ1977)
这题要求严格次小生成树,因此在枚举的时候要注意,不能使可行变换的代价为0。
#include
<
iostream
>
#include < stdio.h >
#include < algorithm >
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
const int MAXN = 100001 , MAXM = 300001 ;
const long long INF = ~ 0Ull >> 2 ;
struct edge {
int a, b, len;
friend bool operator < (edge e0, edge e1) { return e0.len < e1.len;}
} E[MAXM];
struct edge0 {
int a, b, id, pre, next;
} E0[MAXM + MAXM];
int n, m, m0, u[MAXN], pr[MAXN], No[MAXN], s[MAXM], Q[MAXN], fl[MAXN], _s;
long long mst_v = 0 , res;
bool inmst[MAXM], vst[MAXN];
void init()
{
scanf( " %d%d " , & n, & m);
re(i, m) scanf( " %d%d%d " , & E[i].a, & E[i].b, & E[i].len);
}
int find( int x) { int r = x, r0; while (u[r] > 0 ) r = u[r]; while (u[x] > 0 ) {r0 = u[x]; u[x] = r; x = r0;} return r;}
void uni( int s1, int s2) { int tmp = u[s1] + u[s2]; if (u[s1] > u[s2]) {u[s1] = s2; u[s2] = tmp;} else {u[s2] = s1; u[s1] = tmp;}}
void init_d()
{
re1(i, n) {E0[i].a = i; E0[i].pre = E0[i].next = i;}
if (n % 2 ) m0 = n + 1 ; else m0 = n + 2 ;
}
void add_edge( int a, int b, int id)
{
E0[m0].a = a; E0[m0].b = b; E0[m0].id = id; E0[m0].pre = E0[a].pre; E0[m0].next = a; E0[a].pre = m0; E0[E0[m0].pre].next = m0 ++ ;
E0[m0].a = b; E0[m0].b = a; E0[m0].id = id; E0[m0].pre = E0[b].pre; E0[m0].next = b; E0[b].pre = m0; E0[E0[m0].pre].next = m0 ++ ;
}
void prepare()
{
sort(E, E + m);
re1(i, n) u[i] = - 1 ; init_d();
int s1, s2, z = 0 ;
re(i, m) {
s1 = find(E[i].a); s2 = find(E[i].b);
if (s1 != s2) {z ++ ; mst_v += E[i].len; add_edge(E[i].a, E[i].b, i); inmst[i] = 1 ; uni(s1, s2); if (z == n - 1 ) break ;}
}
}
void bfs()
{
re1(i, n) vst[i] = 0 ;
Q[ 0 ] = 1 ; vst[ 1 ] = 1 ;
int i, j;
for ( int front = 0 , rear = 0 ; front <= rear; front ++ ) {
i = Q[front];
for ( int p = E0[i].next; p != i; p = E0[p].next) {
j = E0[p].b;
if ( ! vst[j]) {
vst[j] = 1 ; Q[ ++ rear] = j; pr[j] = i; No[j] = E0[p].id;
}
}
}
}
int LCA( int x, int y)
{
while (x && y) {
if (fl[x] == _s) return x; else fl[x] = _s;
if (fl[y] == _s) return y; else fl[y] = _s;
x = pr[x]; y = pr[y];
}
if (x) while ( 1 ) if (fl[x] == _s) return x; else x = pr[x]; else while ( 1 ) if (fl[y] == _s) return y; else y = pr[y];
}
void sol0( int a, int b, int l)
{
int p = LCA(a, b), p0, No0;
while (a != p) {No0 = No[a]; if ( ! s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[a]; pr[a] = p; a = p0;}
while (b != p) {No0 = No[b]; if ( ! s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[b]; pr[b] = p; b = p0;}
}
void solve()
{
pr[ 1 ] = 0 ; bfs();
re(i, m) if ( ! inmst[i]) {_s = i + 1 ; sol0(E[i].a, E[i].b, E[i].len);}
res = INF;
re(i, m) if (inmst[i] && s[i] && s[i] < res) res = s[i];
res += mst_v;
}
void pri()
{
cout << res << endl;
}
int main()
{
init();
prepare();
solve();
pri();
return 0 ;
}
#include < stdio.h >
#include < algorithm >
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
const int MAXN = 100001 , MAXM = 300001 ;
const long long INF = ~ 0Ull >> 2 ;
struct edge {
int a, b, len;
friend bool operator < (edge e0, edge e1) { return e0.len < e1.len;}
} E[MAXM];
struct edge0 {
int a, b, id, pre, next;
} E0[MAXM + MAXM];
int n, m, m0, u[MAXN], pr[MAXN], No[MAXN], s[MAXM], Q[MAXN], fl[MAXN], _s;
long long mst_v = 0 , res;
bool inmst[MAXM], vst[MAXN];
void init()
{
scanf( " %d%d " , & n, & m);
re(i, m) scanf( " %d%d%d " , & E[i].a, & E[i].b, & E[i].len);
}
int find( int x) { int r = x, r0; while (u[r] > 0 ) r = u[r]; while (u[x] > 0 ) {r0 = u[x]; u[x] = r; x = r0;} return r;}
void uni( int s1, int s2) { int tmp = u[s1] + u[s2]; if (u[s1] > u[s2]) {u[s1] = s2; u[s2] = tmp;} else {u[s2] = s1; u[s1] = tmp;}}
void init_d()
{
re1(i, n) {E0[i].a = i; E0[i].pre = E0[i].next = i;}
if (n % 2 ) m0 = n + 1 ; else m0 = n + 2 ;
}
void add_edge( int a, int b, int id)
{
E0[m0].a = a; E0[m0].b = b; E0[m0].id = id; E0[m0].pre = E0[a].pre; E0[m0].next = a; E0[a].pre = m0; E0[E0[m0].pre].next = m0 ++ ;
E0[m0].a = b; E0[m0].b = a; E0[m0].id = id; E0[m0].pre = E0[b].pre; E0[m0].next = b; E0[b].pre = m0; E0[E0[m0].pre].next = m0 ++ ;
}
void prepare()
{
sort(E, E + m);
re1(i, n) u[i] = - 1 ; init_d();
int s1, s2, z = 0 ;
re(i, m) {
s1 = find(E[i].a); s2 = find(E[i].b);
if (s1 != s2) {z ++ ; mst_v += E[i].len; add_edge(E[i].a, E[i].b, i); inmst[i] = 1 ; uni(s1, s2); if (z == n - 1 ) break ;}
}
}
void bfs()
{
re1(i, n) vst[i] = 0 ;
Q[ 0 ] = 1 ; vst[ 1 ] = 1 ;
int i, j;
for ( int front = 0 , rear = 0 ; front <= rear; front ++ ) {
i = Q[front];
for ( int p = E0[i].next; p != i; p = E0[p].next) {
j = E0[p].b;
if ( ! vst[j]) {
vst[j] = 1 ; Q[ ++ rear] = j; pr[j] = i; No[j] = E0[p].id;
}
}
}
}
int LCA( int x, int y)
{
while (x && y) {
if (fl[x] == _s) return x; else fl[x] = _s;
if (fl[y] == _s) return y; else fl[y] = _s;
x = pr[x]; y = pr[y];
}
if (x) while ( 1 ) if (fl[x] == _s) return x; else x = pr[x]; else while ( 1 ) if (fl[y] == _s) return y; else y = pr[y];
}
void sol0( int a, int b, int l)
{
int p = LCA(a, b), p0, No0;
while (a != p) {No0 = No[a]; if ( ! s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[a]; pr[a] = p; a = p0;}
while (b != p) {No0 = No[b]; if ( ! s[No0] && l > E[No0].len) s[No0] = l - E[No0].len; p0 = pr[b]; pr[b] = p; b = p0;}
}
void solve()
{
pr[ 1 ] = 0 ; bfs();
re(i, m) if ( ! inmst[i]) {_s = i + 1 ; sol0(E[i].a, E[i].b, E[i].len);}
res = INF;
re(i, m) if (inmst[i] && s[i] && s[i] < res) res = s[i];
res += mst_v;
}
void pri()
{
cout << res << endl;
}
int main()
{
init();
prepare();
solve();
pri();
return 0 ;
}