dij的神奇应用

那一天她离我而去

对题解的复述:因为要找从1出发要回到1的最小环,路径一定是1->和1相连的某个点->和1相连的另一个点->1,对和1相连的每一个点跑最短路,用和1相连的剩下的所有点来更新ans,当然还要加上它们各自到1的距离。(dij的性质恰好能保证1不在路径上)

据说以上做法会超时,所以考虑优化。其实不需要每一个点都做一次起点,如果把所有点分成做起点的点和做终点的点,只要保证任意两个点被至少一次分在两个不同的组中……emmm怎么分呢,可以用二进制,先把和1相连的每个点重新编号,每循环到新的一位重新分一遍组(当前位相同为1组),因为数字不同,任意两个数总会有一位一个是0一个是1,所以也就保证了任意两个点被至少一次分在不同的组中。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e4 + 5;
const int N = 4e4 + 2;
const int mod = 1e9 + 7;
const int INF = 1061109567;

int T, n, dis[maxn], vec[maxn], cnt, vd[maxn], s[maxn], sd[maxn];
int t[maxn], td[maxn], pt, ps, m, ans;
bool vis[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int next, to, w;
}a[N<<1];
int head[maxn], len;

void add(int x, int y, int w)
{
    a[++len].to = y; a[len].next = head[x]; a[len].w = w;
    head[x] = len;
}

void dij()//最近魔改dij的真是层出不穷
{
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    //开在函数里面多组数据不用清空,emmm好主意
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
    //分组的方法就是提前把“起点”装进去?而不是把1装进去
    //这种一下子放很多到队列里的让我想到了_来自学长的馈赠7_道路和航线
    for(int i=1; i<=ps; i++)
    {
        q.push(make_pair(sd[i], s[i])); dis[s[i]] = sd[i];
    }
    vis[1] = 1;
    while(!q.empty())
    {
        int u = q.top().second; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i=head[u]; i; i=a[i].next)
        {
            int v = a[i].to, ds = a[i].w;
            if(vis[v]) continue;
            if(dis[v] > dis[u] + ds)
            {
                dis[v] = dis[u] + ds;
                q.push(make_pair(dis[v], v));
            }
        }
    }
    for(int i=1; i<=pt; i++)
    {
        //printf("ans = %d\n", ans);
        ans = min(ans, dis[t[i]]+td[i]);
    }
}

int main()
{
    freopen("leave.in", "r", stdin);
    freopen("leave.out", "w", stdout);
    
    T = read();
    while(T--)
    {
        ans = 0x3f3f3f3f;
        cnt = len = 0;
        memset(head, 0, sizeof(head));

        n = read(); m = read();
        for(int i=1; i<=m; i++)
        {
            int x = read(), y = read(), z = read();
            add(x, y, z); add(y, x, z);
        }
        for(int i=head[1]; i; i=a[i].next)//和1相连
        {
            vec[++cnt] = a[i].to; vd[cnt] = a[i].w;
        }
        if(!cnt)
        {
            printf("-1\n"); continue;
        }

        for(int i=1,j=1; j<=15; i<<=1, j++)
        {
            ps = pt = 0;
            for(int k=1; k<=cnt; k++)
            {
                if(k&i) s[++ps] = vec[k], sd[ps] = vd[k];
                else t[++pt] = vec[k], td[pt] = vd[k];
            }
            if(!ps || !pt) continue;
            dij();
        }
        if(ans != 0x3f3f3f3f) printf("%d\n", ans);
        else printf("-1\n");
    }

    return 0;
}
View Code

 

最短路

这个是有点权,也是往返,已经经过的点权不用重复记录,错误的思路是直接点权转边权之后先从1到n再从n到1,从1到n的那一次记录路径跑完之后把路径上的点的出边修改为0,考虑一种特殊情况,从1到n的最优路径全程单向不能重复利用,而另一条不优一点的路径确是“双向的”,所以按以上思路贪心的话就导致多走了一段。

我才知道原来dij还可以用bitset来记录路径。

理解题解的时候瞎写了一些前言不搭后语的话……

  正图和反图找的都是重复经过的点点权不重复记录的单源最短路
    除了经过的点之外,其他信息都不共用
    但问题是,经过的点在什么时间相互影响,什么时间还没影响,两条路应该有先后顺序吗?
    这个pre数组应该是最核心的思路了
    先后顺序不存在,因为一个点不管是谁先走了都一样
    可是在找最优路径的过程中先后顺序是有用的,所以它其实是把对应的每一种时间顺序都算出来了?
    dis的两维对应的时间是同步的
    只有状态相同时,pre数组才可以共用,没有了之间先后的问题,但是要求路径一致
    “经过的点”不能传递的情况就是我从一个点到另一个点走了两条不同的路线
    其中有一条路线上经过的点的记录就是无效的
    到同一个点的最短路是固定下来的,但是到不同的点就不一样了
    所以相同目的地经过的点共用,否则无关
    但是这样做有没有把去程和返程分隔开呢??一个来回的经过点是必须要共用的。。
    当然没有分开,pre存的本来就是两个图从一个1到x一个从1到y中间从1到x当然算上了
    写了这么多,还是,,感性理解,,,
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 255;
const int N = 1e7 + 2;
const int mod = 1e9 + 7;
const ll INF = 1e18;

int ans, n, m, c[maxn], dis[maxn][maxn];
bool v[maxn][maxn];
bitset<maxn> pre[maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int to, next;
}a[200010];
int head[maxn][2], len;

void add(int x, int y, int id)
{
    a[++len].to = y; a[len].next = head[x][id]; head[x][id] = len;
}

struct stu
{
    int x, y, w;
    bool operator < (const stu &T) const 
    {
        return w > T.w;
    }
}g;
priority_queue<stu> q;

void dij(int st)
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1][1] = c[1]; pre[1][1][1] = 1;
    g.x = g.y = 1; g.w = dis[1][1]; q.push(g);
    while(!q.empty())
    {
        int x = q.top().x, y = q.top().y; q.pop();
        //正向取出x,反向取出y,这两维之间没有联系完全分割
        if(!v[x][y])
        {
            for(int i=head[x][0]; i; i=a[i].next)
            {
                int ret = dis[x][y];
                int v = a[i].to;
                if(!pre[x][y][v]) ret += c[v];//如果到达x的路径上没有v
                if(dis[v][y] > ret)
                {
                    dis[v][y] = ret;
                    //到达v的路径需要经过x,也就需要经过到达x经过的点,当然也经过了v
                    //据说开bitset的优点就在于直接赋值
                    pre[v][y] = pre[x][y]; pre[v][y][v] = 1;
                    g.x = v; g.y = y; g.w = dis[v][y];
                    q.push(g);
                }
            }
            for(int i=head[y][1]; i; i=a[i].next)
            {
                int ret = dis[x][y];
                int v = a[i].to;
                if(!pre[x][y][v]) ret += c[v];
                if(dis[x][v] > ret)
                {
                    dis[x][v] = ret;
                    pre[x][v] = pre[x][y]; pre[x][v][v] = 1;
                    g.x = x; g.y = v; g.w = dis[x][v];
                    q.push(g);
                }
            }
        }
    }
}

int main()
{
    n = read(); m = read();
    for(int i=1; i<=n; i++) c[i] = read();
    for(int i=1; i<=m; i++)
    {
        int u = read(), v = read();
        add(u, v, 0); add(v, u, 1);//哇哦,这就是传说中的反图!?
    }
    dij(1);
    if(dis[n][n] == 0x3f3f3f3f) printf("-1");
    else printf("%d", dis[n][n]);

    return 0;
}
View Code

原来的代码好像出了个小漏洞:dij里的v数组根本就没有发挥作用。。

这道题还涉及到了另一个套路,关于“不能重复的路径”,要么是重复不合法,要么是重复的贡献不能累加,根据这一层理解就可以得到一些新的思考,似乎这个题可以不感性。 

方格取数:
每次遇到这种走两次,其中一次走后有清空的题,就会忘了怎么办。。
按说这是一个应该记住的套路
好像考过一个图论的题,当时写了个假做法似乎分还不少,正解忘了
emmm还有一个传纸条
如果我真的dp两次+记录路径,会假吗??
WA 80 啊对当然会假,,我居然一遍敲对了记录路径!?
反例 
0 3 5
2 6 0
0 4 0
第一次 0 3 6 4 0 第二次 0 (3) 5 0 0 ans = 18
事实上 0 3 5 0 0  + 0 2 6 4 0 更优
解决方案本来应该四维dp,可以优化成3维,好像还有二维的tql

传纸条:
和上面那个的区别就是走过的路径不能重复还有数据超范围,但是解决方案中的区别是什么呢?
我当时好像直接CV一下两个题都A了,why?
路径不合法到底有没有可能会影响答案?
if(i == j) continue;
找不同发现这个a也很闹鬼,同样的定义,一个是a[k-i+1][i],另一个直接a[k-i][i]
不理解。。。

找到了:暑假集训1--最短路!!!
假做法只有20分,分不少什么的。。。记性有问题!
这题当时好像是感性理解的,再理解一遍发现它不难,因为当时理解了半天的
“为什么可以同时”忽然变得很显然
好像还发现AC代码有bug,比如vis数组记录了个寂寞。。
这些题告诉我们:不能重复经过的问题的解决方案就是同时进行,如果一正一反可以转化一下

对标记的问题我专门改了一版新的:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 255;

int ans, n, m, c[maxn], dis[maxn][maxn];
bool vis[maxn][maxn];
bitset<maxn> pre[maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int next, to;
}a[200010];
int head[maxn][2], len;//差点忘了建两个图的方便写法,每次都h+tot来个新结构体
//然而就更需要注意a数组的大小了!

void add(int x, int y, int id)
{
    a[++len].to = y; a[len].next = head[x][id];
    head[x][id] = len;
}

struct stu
{
    int x, y, w;
    bool operator < (const stu &T) const 
    {
        return w > T.w;//top取最小值
    }
}g;
priority_queue<stu> q;

void dij(int st)
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1][1] = c[1]; pre[1][1][1] = 1;
    g.x = g.y = 1; g.w = dis[1][1]; q.push(g);
    while(!q.empty())
    {
        int x = q.top().x, y = q.top().y; q.pop();
        if(!vis[x][y])
        {
            vis[x][y] = 1;
            for(int i=head[x][0]; i; i=a[i].next)
            {
                int ret = dis[x][y];
                int v = a[i].to;
                if(!pre[x][y][v]) ret += c[v];
                if(dis[v][y] > ret)
                {
                    dis[v][y] = ret;
                    pre[v][y] = pre[x][y]; pre[v][y][v] = 1;
                    g.x = v; g.y = y; g.w = dis[v][y];
                    q.push(g);
                }
            }
            for(int i=head[y][1]; i; i=a[i].next)
            {
                int ret = dis[x][y];
                int v = a[i].to;
                if(!pre[x][y][v]) ret += c[v];
                if(dis[x][v] > ret)
                {
                    dis[x][v] = ret;
                    pre[x][v] = pre[x][y]; pre[x][v][v] = 1;
                    g.x = x; g.y = v; g.w = dis[x][y];
                    q.push(g);
                }
            }
        }
    }
}

int main()
{
    n = read(); m = read();
    for(int i=1; i<=n; i++) c[i] = read();
    for(int i=1; i<=m; i++)
    {
        int u = read(), v = read();
        add(u, v, 0); add(v, u, 1);
    }
    dij(1);
    if(dis[n][n] == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", dis[n][n]);

    return 0;
}
View Code

 

枚举计算

有向图,直接跑最短路输出都能水到一些分,可以我建了双向边还输出了一个dis[maxn]!?

一个点可以给另一个点发护盾,到达一个点是到达另一个点的条件,清除护盾和走向目标可以由无限个person同时进行,不过都要从1出发。用dij跑最短路的时候循环两次,不能进入的点不能更新,dij把最长的距离最后拿出的性质保证了把点的入度减成0的点就是耗时最大的那个(对答案造成影响的那个),vector存点的入边也是奇妙操作。

虽然每个点在入度减为0的时候都被它的所有入边连接的点更新过,但是每个点还是要循环更新它指向的点,因为有的点很可怜没人保护它,入度本来就是0就没有被减为0的时候了。

      in减小的顺序不确定,可是d[y]只被最后一个使它减小的点更新,怎么证明正确性?
            意思就是:怎么肯定d[x]是所有能给y提供保护的点中最大的那个?
            有一个还没清空就走不了啊,所以它应该被每一个盾牌产生器更新一遍才对??
            还有:如果y已经是0了……这种应该不存在,因为每个x只会被拿出来一遍

            因为是优先队列,所以d一定以递增的顺序被取出
            但是如果加上时间的延迟呢?。。没有时间的延迟啊,都是入度减为0的时刻。。。
            好像,,似乎,,理解了??
#include <bits/stdc++.h>

using namespace std;

#define Catherine 专业抄题解100年
typedef long long ll;
const int maxn = 3007;
const int mod = 1e9 + 7;
const int maxm = 70007;

int in[maxn], d[maxn], n, m;
bool v[maxn];
priority_queue<pair<int, int> > q;
vector<int> a[maxn];
vector<pair<int, int> > u[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct edge 
{
    int next, to, w;
}e[maxm];
int head[maxn], len;

void add(int x, int y, int z)
{
    e[++len].to = y; e[len].next = head[x]; e[len].w = z;
    head[x] = len;
    u[y].push_back(make_pair(x, z));
}

void dij()
{
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    q.push(make_pair(0, 1));
    while(!q.empty())
    {
        int x = q.top().second; q.pop();
        v[x] = 1;//居然忘了打标记!这都能过样例可见什么?
        //in[x]--;x的入度当然已经减没了,到不了的点是不可能入队的好吧
        //if(v[x]) continue; 题解上没有这一步,解释了最后一句不是重复
        for(int i=0; i<(int)a[x].size(); i++)
        {
            int y = a[x][i];
            in[y]--;
            if(!in[y])//实践证明,!符号只判断恰好为0
            {
                //因为这不是一棵树,循环每个点的多个祖先
                //所以y可以被visit过吗
                for(int j=0; j<(int)u[y].size(); j++)
                {
                    d[y] = min(d[y], max(d[u[y][j].first]+u[y][j].second, d[x]));
                }
                q.push(make_pair(-d[y], y));//这一步被我落掉了
                //可以更新其他点的都可以入队,y已经被所有可能性更新过,已经固定死了
            }
        }
        //1.更新所保护的点 2.更新所指向的点(针对入度本来就是0)
        for(int i=head[x]; i; i=e[i].next)
        {
            int y = e[i].to;
            if(!v[y] && !in[y])
            {
                if(d[y] > d[x] + e[i].w)
                {
                    d[y] = d[x] + e[i].w;
                    q.push(make_pair(-d[y], y));
                }
            }
        }
        while(!q.empty() && v[q.top().second]) q.pop();//这一步操作是重复的吗?
    }
}

int main()
{
    n = read(); m = read();
    for(int i=1; i<=m; i++)
    {
        int x = read(), y = read(), z = read();
        add(x, y, z);
    }
    for(int i=1; i<=n; i++)
    {
        int t = read();
        for(int j=1; j<=t; j++)
        {
            int x = read();
            a[x].push_back(i);
            in[i]++;
        }
    }
    dij();
    printf("%d\n", d[n]);

    return 0;
}
View Code

 

道路和航线

有负边权的最短路,看起来就是spfa的板子,然而……[友情提示:关于****,它*了]我当时居然没有看懂提示,就是提醒我们spfa被数据特意卡掉了。

数据还是比较良心的,直接spfa可以拿到30分,但是我把spfa里的队列改成了栈,不是魔改就是单纯的写错了板子,在我记得应该用队列的情况下——论Cat最近一直考的很菜的原因,看看这群低错就知道了……

把没有负边权的点看成一个整体,这些整体加上负数边就形成了一条链,所以要在每个区域内跑dij,在区域外拓扑排序,对dij的改动就是每次把这一个整体中所有的点放进队列,至于怎么划分区域,dfs染色就好了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e5 + 100;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const int lim = 1e4 + 1;

vector<int> block[maxn];
queue<int> q;
int t, r, p, s, cnt, dis[maxn], du[maxn], id[maxn];
bool st[maxn];
typedef pair<int, int> PII;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int next, to, w;
}a[maxn];
int head[maxn], len;

void add(int x, int y, int w)
{
    a[++len].to = y; a[len].next = head[x]; a[len].w = w;
    head[x] = len;
}

void dfs(int u, int num)
{
    id[u] = num; block[num].push_back(u);
    for(int i=head[u]; i; i=a[i].next)
    {
        int v = a[i].to;
        if(!id[v]) dfs(v, num);
    }
}

void dij(int num)
{
    priority_queue<PII, vector<PII>, greater<PII> > heap;
    for(int i=0; i<block[num].size(); i++)
    {
        heap.push({dis[block[num][i]], block[num][i]});
    }
    while(heap.size())
    {
        PII t = heap.top();
        heap.pop();
        int u = t.second;
        if(st[u]) continue;
        st[u] = 1;
        for(int i=head[u]; i; i=a[i].next)
        {
            int v = a[i].to;
            if(id[v] != id[u] && (--du[id[v]])==0) q.push(id[v]);
            if(dis[v] > dis[u]+a[i].w)
            {
                dis[v] = dis[u]+a[i].w;
                if(id[v] == id[u]) 
                {
                    heap.push({dis[v], v});
                }
            }
        }
    }
}

void topsort()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    for(int i=1; i<=cnt; i++)
    {
        if(!du[i]) q.push(i);//入度为0的连通快加入队列
    }
    while(q.size())
    {
        int t = q.front();
        q.pop();
        dij(t);//在每一个连通块里最短路
    }
}

int main()
{
    t = read(); r = read(); p = read(); s = read();
    for(int i=1; i<=r; i++)
    {
        int x = read(), y = read(), w = read();
        add(x, y, w); add(y, x, w);
    }
    for(int i=1; i<=t; i++)
    {
        if(!id[i])
        {
            cnt++; dfs(i, cnt);
        }
    }
    for(int i=1; i<=p; i++)
    {
        int x = read(), y = read(), w = read();
        add(x, y, w);
        du[id[y]]++;
    }
    topsort();
    for(int i=1; i<=t; i++)
    {
        if(dis[i] > INF/2) 
        {
            printf("NO PATH\n");
        }
        else printf("%d\n", dis[i]);
    }
    
    return 0;
}
View Code

不过关于数据针对spfa这个问题,随机化spfa也能解决,但是好像在某些OJ上提交srand(time(0))会导致“运行错误”也就是RE……

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e5 + 100;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const int lim = 1e4 + 1;

int t, r, p, s;
bool vis[maxn];
ll dis[maxn];
deque<int> q;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int next, to, w;
}a[maxn];
int head[maxn], len;

void add(int x, int y, int w)
{
    a[++len].to = y; a[len].next = head[x]; a[len].w = w;
    head[x] = len;
}

void spfa()
{
    dis[s] = 0;
    q.push_front(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop_front();
        vis[u] = 0;
        for(int i=head[u]; i; i=a[i].next)
        {
            int v = a[i].to;
            if(dis[v] > dis[u]+a[i].w)
            {
                dis[v] = dis[u]+a[i].w;
                if(!vis[v])
                {
                    if(rand()&1) q.push_back(v);
                    else q.push_front(v);
                    vis[v] = 1;
                }
            }
        }
    }
}

int main()
{
    srand(time(0));
    t = read(); r = read(); p = read(); s = read();
    for(int i=1; i<=r; i++)
    {
        int x = read(), y = read(), w = read();
        add(x, y, w); add(y, x, w);
    }
    for(int i=1; i<=p; i++)
    {
        int x = read(), y = read(), w = read();
        add(x, y, w);
    }
    for(int i=1; i<=t; i++)
    {
        dis[i] = 1e17;
    }
    spfa();
    for(int i=1; i<=t; i++)
    {
        if(dis[i] >= 1e16)
        {
            printf("NO PATH\n");
        }
        else printf("%lld\n", dis[i]);
    }
    
    return 0;
}
随机化spfa

 

Possible

树上两点间的距离很好求,所以给树加了一些反祖边,返祖边和树边是分开添加的,这就可以建两个图,对每一个点跑它到它的k级祖先的最短路,因为两个点的距离一定由这两个点分别到它们的同一个某某祖先(没有返祖边直接是LCA)的两段路径构成。至于那个祖先有幸成为中转点——暴力枚举就好了,从LCA开始跳到头。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 2;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const int lim = 1e4 + 1;

bool vis[maxn], can[maxn][33];
int top[maxn], siz[maxn], dep[maxn], fa[maxn], n, m, q, son[maxn];
ll dis[maxn][33];
priority_queue< pair<ll, int> > que;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

const int N = 1e6 + 10;
struct node 
{
    int next, to, w;
}a[N], e[N];
int head[N], len, h[N], tot;

void add_tree(int x, int y, int w)
{
    a[++len].to = y; a[len].next = head[x]; a[len].w = w;
    head[x] = len;
}

void add(int x, int y, int w)
{
    e[++tot].to = y; e[tot].next = h[x]; e[tot].w = w;
    h[x] = tot;
}

void find_heavy_edge(int u, int fat, int depth)
{
    fa[u] = fat;
    dep[u] = depth;
    siz[u] = 1;
    son[u] = 0;
    int maxsize = 0;
    //printf("u = %d\n", u);

    for(int i=head[u]; i; i=a[i].next)
    {
        int v = a[i].to;
        if(dep[v]) continue;
        find_heavy_edge(v, u, depth+1);
        siz[u] += siz[v];
        if(siz[v] > maxsize)
        {
            maxsize = siz[v];
            son[u] = v;
        }
    }
}

void connect_heavy_edge(int u, int ancestor)
{
    top[u] = ancestor;
    if(son[u])
    {
        connect_heavy_edge(son[u], ancestor);
    }
    for(int i=head[u]; i; i=a[i].next)
    {
        int v = a[i].to;
        if(v == son[u] || v == fa[u]) continue;
        connect_heavy_edge(v, v);
    }
}

int LCA(int x, int y)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        //printf("x = %d\n", x);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x, y);
    return x;
}

void dijstra(int x)
{
    dis[x][0] = 0;
    que.push(make_pair(0, x));
    pair<ll, int> ato;
    while(!que.empty())
    {
        ato = que.top();
        que.pop();
        if(can[ato.second][dep[ato.second]-dep[x]]) continue;
        can[ato.second][dep[ato.second]-dep[x]] = 1;

        for(int i=h[ato.second]; i; i=e[i].next)
        {
            int v = e[i].to;
            if(vis[v]) continue;//不在x子树内
            if(dis[v][dep[v]-dep[x]] > dis[ato.second][dep[ato.second]-dep[x]]+e[i].w)
            {
                dis[v][dep[v]-dep[x]] = dis[ato.second][dep[ato.second]-dep[x]]+e[i].w;
                que.push(make_pair(-dis[v][dep[v]-dep[x]], v));
            }
        }
    }
}

void dfs_dij(int x)
{
    dijstra(x);
    //printf("x = %d\n", x);
    vis[x] = 1;
    for(int i=head[x]; i; i=a[i].next)
    {
        int v = a[i].to;
        if(v == fa[x]) continue;
        dfs_dij(v);
    }
}

int main()
{
    n = read(); m = read(); q = read();
    for(int i=1; i<n; i++)
    {
        int x = read(), y = read(), w = read();
        add_tree(x, y, w); add_tree(y, x, w);
        add(x, y, w); add(y, x, w);
    }
    for(int i=n; i<=m; i++)
    {
        int x = read(), y = read(), w = read();
        add(x, y, w); add(y, x, w);
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=30; j++)
        {
            dis[i][j] = 1e18;
        }
    }
    find_heavy_edge(1, 0, 1);
    connect_heavy_edge(1, 1);
    dfs_dij(1);
    //printf("111\n");
    while(q--)
    {
        int u = read(), v = read();
        //printf("u = %d v = %d\n", u, v);
        if(dep[u] > dep[v]) swap(u, v);
        int lca = LCA(u, v);
        if(lca == u)
        {
            ll MinDis = dis[v][dep[v]-dep[u]];
            while(fa[lca])
            {
                //printf("lca = %d\n", lca);
                lca = fa[lca];
                MinDis = min(MinDis, dis[v][dep[v]-dep[lca]]+dis[u][dep[u]-dep[lca]]);
            }
            printf("%lld\n", MinDis);
        }
        else 
        {
            ll MinDis = dis[v][dep[v]-dep[lca]]+dis[u][dep[u]-dep[lca]];
            while(fa[lca])
            {
                lca = fa[lca];
                MinDis = min(MinDis, dis[v][dep[v]-dep[lca]]+dis[u][dep[u]-dep[lca]]);
            }
            printf("%lld\n", MinDis);
        }
    }
    
    return 0;
}
View Code

 

posted @ 2022-08-12 18:03  Catherine_leah  阅读(29)  评论(0编辑  收藏  举报
/* */