#include <bits/stdc++.h>
using namespace std;

using vi = vector<int>;
using ll = long long;

#define rep(i,a,b) for(auto i = (a); i < (b); ++i)
#define all(a) begin(a),end(a)
#define sz(a) (int)size(a)
#define PB push_back

struct edge{int to; ll w;};
using graph = vector<vector<edge>>;

using state = pair<ll, int>; // w, curr
const ll INF = 1e15;

auto maxx(auto& a, auto b){a = max(a, b);}

auto dijkstra(int n, graph& g, int k) {
    vi visited(n);
    vector<ll> dists(n, INF);
    priority_queue<state, vector<state>, greater<state>> pq;
    rep(i, 0, k) {
        dists[i] = 0;
        pq.push({0LL, i});
    }
    while (!pq.empty()) {
        auto [dist, curr] = pq.top();
        pq.pop();
        if (visited[curr]) continue;
        visited[curr] = true;
        for (auto [to, w]: g[curr]) {
            if (dist + w < dists[to]) {
                dists[to] = dist + w;
                pq.push({dist + w, to});
            }
        }
    }
    return dists;
}

void root(int i, int p, const graph& g, vi& pars, vector<ll>& pdists, vi& depth,
          ll pdist = 0, int dep = 0) {
    pars[i] = p; pdists[i] = pdist;
    depth[i] = dep;
    for (auto e: g[i]) {
        if (e.to == p) continue;
        root(e.to, i, g, pars, pdists, depth, e.w, dep + 1);
    }
}

struct DSU {
    vi pars;
    DSU(int n) : pars(n) {iota(all(pars), 0);}
    int find(int i) {return (pars[i] == i) ? i : (pars[i] = find(pars[i]));}
    void merge(int a, int b) {
        a = find(a); b = find(b);
        pars[a] = b;
    } 
};

graph mst(int n, graph &g) {
    vector<tuple<ll, int, int>> edges;
    rep(i, 0, n) {
        for (auto& [to, w]: g[i]) {
            edges.PB({w, i, to});
        }
    }
    sort(all(edges));
    graph t(n);
    DSU dsu(n);
    for (auto [w, a, b]: edges) {
        if (dsu.find(a) != dsu.find(b)) {
            dsu.merge(a, b);
            t[a].PB({b, w});
            t[b].PB({a, w});
        }
    }
    return t;
}

auto treeJump(vi& P, vector<ll>& D){
	int on = 1, d = 1;
	while(on < sz(P)) on *= 2, d++;
	vector<vi> jmp(d, P);
        vector<vector<ll>> mx(d, D);
	rep(i,1,d) rep(j,0,sz(P)) {
            jmp[i][j] = jmp[i-1][jmp[i-1][j]];
            mx[i][j] = max(mx[i - 1][j], mx[i-1][jmp[i-1][j]]);
        }
	return pair{jmp, mx};
}

auto jmp(vector<vi>& tbl, vector<vector<ll>>& mx, int nod, int steps){
    ll maxdist = 0;
    rep(i,0,sz(tbl)) {
        if(steps&(1<<i)) {
            maxdist = max(maxdist, mx[i][nod]);
            nod = tbl[i][nod];
        }
    }
    return pair{maxdist, nod};
}

int maxedge(vector<vi>& tbl, vector<vector<ll>>& mx, vi& depth, int a, int b) {
    ll acc = 0;
    if (depth[a] < depth[b]) swap(a, b);
    auto [d_up, up] = jmp(tbl, mx, a, depth[a] - depth[b]);
    a = up;
    maxx(acc, d_up);
    if (a == b) return acc;
    for (int i = sz(tbl); i--;) {
        int c = tbl[i][a], d = tbl[i][b];
        if (c != d) {
            maxx(acc, mx[i][a]);
            maxx(acc, mx[i][b]);
            a = c, b = d;
        }
    }
    return max({acc, mx[0][a], mx[0][b]});
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k, q;
    cin >> n >> m >> k >> q;

    graph g(n);
    rep(i, 0, m) {
        int a, b, w;
        cin >> a >> b >> w;
        --a; --b;
        g[a].PB({b, w});
        g[b].PB({a, w});
        assert(w >= 0);
    }

    vector<ll> distances = dijkstra(n, g, k);
    rep(i, 0, n)
        for (auto& [to, w]: g[i]) {
            w += distances[i] + distances[to];
            assert(w >= 0);
        }
    auto meta = mst(n, g);
    
    vi pars(n);
    vector<ll> pardists(n);
    vi depth(n);
    root(0, 0, meta, pars, pardists, depth);
    auto [jmp, jmpmax] = treeJump(pars, pardists);
    rep(i, 0, q) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        auto res = maxedge(jmp, jmpmax, depth, a, b);
        cout << res << '\n';

    }
}
