dij的神奇应用
那一天她离我而去
对题解的复述:因为要找从1出发要回到1的最小环,路径一定是1->和1相连的某个点->和1相连的另一个点->1,对和1相连的每一个点跑最短路,用和1相连的剩下的所有点来更新ans,当然还要加上它们各自到1的距离。(dij的性质恰好能保证1不在路径上)
据说以上做法会超时,所以考虑优化。其实不需要每一个点都做一次起点,如果把所有点分成做起点的点和做终点的点,只要保证任意两个点被至少一次分在两个不同的组中……emmm怎么分呢,可以用二进制,先把和1相连的每个点重新编号,每循环到新的一位重新分一遍组(当前位相同为1组),因为数字不同,任意两个数总会有一位一个是0一个是1,所以也就保证了任意两个点被至少一次分在不同的组中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
最短路
这个是有点权,也是往返,已经经过的点权不用重复记录,错误的思路是直接点权转边权之后先从1到n再从n到1,从1到n的那一次记录路径跑完之后把路径上的点的出边修改为0,考虑一种特殊情况,从1到n的最优路径全程单向不能重复利用,而另一条不优一点的路径确是“双向的”,所以按以上思路贪心的话就导致多走了一段。
我才知道原来dij还可以用bitset来记录路径。
理解题解的时候瞎写了一些前言不搭后语的话……
正图和反图找的都是重复经过的点点权不重复记录的单源最短路
除了经过的点之外,其他信息都不共用
但问题是,经过的点在什么时间相互影响,什么时间还没影响,两条路应该有先后顺序吗?
这个pre数组应该是最核心的思路了
先后顺序不存在,因为一个点不管是谁先走了都一样
可是在找最优路径的过程中先后顺序是有用的,所以它其实是把对应的每一种时间顺序都算出来了?
dis的两维对应的时间是同步的
只有状态相同时,pre数组才可以共用,没有了之间先后的问题,但是要求路径一致
“经过的点”不能传递的情况就是我从一个点到另一个点走了两条不同的路线
其中有一条路线上经过的点的记录就是无效的
到同一个点的最短路是固定下来的,但是到不同的点就不一样了
所以相同目的地经过的点共用,否则无关
但是这样做有没有把去程和返程分隔开呢??一个来回的经过点是必须要共用的。。
当然没有分开,pre存的本来就是两个图从一个1到x一个从1到y中间从1到x当然算上了
写了这么多,还是,,感性理解,,,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
原来的代码好像出了个小漏洞: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数组记录了个寂寞。。
这些题告诉我们:不能重复经过的问题的解决方案就是同时进行,如果一正一反可以转化一下
对标记的问题我专门改了一版新的:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
枚举计算
有向图,直接跑最短路输出都能水到一些分,可以我建了双向边还输出了一个dis[maxn]!?
一个点可以给另一个点发护盾,到达一个点是到达另一个点的条件,清除护盾和走向目标可以由无限个person同时进行,不过都要从1出发。用dij跑最短路的时候循环两次,不能进入的点不能更新,dij把最长的距离最后拿出的性质保证了把点的入度减成0的点就是耗时最大的那个(对答案造成影响的那个),vector存点的入边也是奇妙操作。
虽然每个点在入度减为0的时候都被它的所有入边连接的点更新过,但是每个点还是要循环更新它指向的点,因为有的点很可怜没人保护它,入度本来就是0就没有被减为0的时候了。
in减小的顺序不确定,可是d[y]只被最后一个使它减小的点更新,怎么证明正确性?
意思就是:怎么肯定d[x]是所有能给y提供保护的点中最大的那个?
有一个还没清空就走不了啊,所以它应该被每一个盾牌产生器更新一遍才对??
还有:如果y已经是0了……这种应该不存在,因为每个x只会被拿出来一遍
因为是优先队列,所以d一定以递增的顺序被取出
但是如果加上时间的延迟呢?。。没有时间的延迟啊,都是入度减为0的时刻。。。
好像,,似乎,,理解了??
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
道路和航线
有负边权的最短路,看起来就是spfa的板子,然而……[友情提示:关于****,它*了]我当时居然没有看懂提示,就是提醒我们spfa被数据特意卡掉了。
数据还是比较良心的,直接spfa可以拿到30分,但是我把spfa里的队列改成了栈,不是魔改就是单纯的写错了板子,在我记得应该用队列的情况下——论Cat最近一直考的很菜的原因,看看这群低错就知道了……
把没有负边权的点看成一个整体,这些整体加上负数边就形成了一条链,所以要在每个区域内跑dij,在区域外拓扑排序,对dij的改动就是每次把这一个整体中所有的点放进队列,至于怎么划分区域,dfs染色就好了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
不过关于数据针对spfa这个问题,随机化spfa也能解决,但是好像在某些OJ上提交srand(time(0))会导致“运行错误”也就是RE……
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
Possible
树上两点间的距离很好求,所以给树加了一些反祖边,返祖边和树边是分开添加的,这就可以建两个图,对每一个点跑它到它的k级祖先的最短路,因为两个点的距离一定由这两个点分别到它们的同一个某某祖先(没有返祖边直接是LCA)的两段路径构成。至于那个祖先有幸成为中转点——暴力枚举就好了,从LCA开始跳到头。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }