#include <bits/stdc++.h>
using i64 = long long;
using P = std::tuple<i64, int, int>;

class UF {
    int *par;
    int *rank;

    public:
    UF(int size) : par(new int[size]), rank(new int[size]()) {
        for (int i = 0; i < size; i++) par[i] = i;
    }
    ~UF() { delete[] par; delete[] rank; }

    int root(int x) {
        if (par[x] == x) return x;
        return par[x] = root(par[x]);
    }

    bool same(int x, int y) {
        return root(x) == root(y);
    }

    void unite(int x, int y) {
        x = root(x);
        y = root(y);
        if (x == y) return;

        if (rank[x] < rank[y]) par[x] = y;
        else {
            par[y] = x;
            if (rank[x] == rank[y]) rank[x]++;
        }
    }
};

struct HLDecomposition {
    using Graph = std::vector<std::deque<int>>;
    int size;
    Graph g;
    std::vector<int> par, depth, chs, heavy, decomposed, inv, head;

protected:
    void dfs(int root) {
        std::deque<std::pair<int, unsigned>> q { { root, 0 } };
        par[root] = -1;
        depth[root] = 0;

        while (!q.empty()) {
            auto &v = q.back();
            if (v.second < g[v.first].size()) {
                auto ch = g[v.first][v.second++];
                if (ch == par[v.first]) continue;
                par[ch] = v.first;
                depth[ch] = depth[v.first] + 1;
                q.emplace_back(ch, 0);
            } else {
                int h = -1;
                for (int i = 0; i < g[v.first].size(); i++) {
                    auto ch = g[v.first][i];
                    if (ch == par[v.first]) continue;
                    chs[v.first] += chs[ch];
                    if (h < 0 || chs[g[v.first][h]] < chs[ch]) h = i;
                }
                if (h >= 0) {
                    heavy[v.first] = g[v.first][h];
                    if (h > 0) std::swap(g[v.first][h], g[v.first][0]);
                }
                q.pop_back();
            }
        }
    }

    void bfs(int root) {
        static int next = 0;
        std::deque<int> q { root };

        while (!q.empty()) {
            int v = q.front();
            q.pop_front();
            for (int i = v; i >= 0; i = heavy[i]) {
                decomposed[i] = next;
                inv[next++] = i;
                head[i] = v;
                for (int ot : g[i]) {
                    if (ot == par[i] || ot == heavy[i]) continue;
                    q.push_back(ot);
                }
            }
        }
    }

public:
    HLDecomposition() = delete;
    HLDecomposition(int size)
        : size(size)
        , g(size)
        , par(size)
        , depth(size)
        , chs(size, 1)
        , heavy(size, -1)
        , decomposed(size)
        , inv(size)
        , head(size) {}
    HLDecomposition(const Graph &gr)
        : size(gr.size())
        , g(gr)
        , par(size)
        , depth(size)
        , chs(size, 1)
        , heavy(size, -1)
        , decomposed(size)
        , inv(size)
        , head(size) {}
    HLDecomposition(Graph &&gr)
        : size(gr.size())
        , g(gr)
        , par(size)
        , depth(size)
        , chs(size, 1)
        , heavy(size, -1)
        , decomposed(size)
        , inv(size)
        , head(size) {}

    void add_edge(int u, int v) {
        g[u].push_back(v);
        g[v].push_back(u);
    }

    void build(int root = 0) {
        dfs(root);
        bfs(root);
    }

    int lca(int u, int v) {
        while (head[u] != head[v]) {
            if (decomposed[u] < decomposed[v]) v = par[head[v]];
            else u = par[head[u]];
        }
        return decomposed[u] < decomposed[v] ? u : v;
    }

    template <typename F>
    void for_each(int u, int v, const F &f) {
        while(true) {
            if (decomposed[u] > decomposed[v]) std::swap(u, v);
            f(std::max(decomposed[head[v]], decomposed[u]), decomposed[v] + 1);
            if (head[u] == head[v]) return;
            v = par[head[v]];
        }
    }

    template <typename F>
    void for_each_edge(int u, int v, const F &f) {
        while (true) {
            if (decomposed[u] > decomposed[v]) std::swap(u, v);
            if (head[u] == head[v]) {
                if (u != v) f(decomposed[u] + 1, decomposed[v] + 1);
                return;
            }
            f(decomposed[head[v]], decomposed[v] + 1);
            v = par[head[v]];
        }
    }
};

template <typename T, typename F>
class SegmentTree {
    int size;
    T id;
    T *dat;
    F f;

    T query_impl(const int a, const int b, const int k, const int l, const int r) const {
        if (r <= a || b <= l) return id;
        if (a <= l && r <= b) return dat[k];
        return f(query_impl(a, b, k * 2 + 1, l, (l + r) / 2),
                 query_impl(a, b, k * 2 + 2, (l + r) / 2, r));
    }

public:
    SegmentTree(const int n, const T &id, const F f) : id(id), f(f) {
        size = 2;
        while (size < n) size *= 2;
        dat = new T[2 * size - 1];
        for (int i = 0; i < 2 * size - 1; i++) dat[i] = id;
    }

    template <template <class> class Container>
    SegmentTree(const Container<T> &v, const T &id, const F f) : id(id), f(f) {
        size = 2;
        int n = v.size();
        while (size < n) size *= 2;
        dat = new T[2 * size - 1];
        for (int i = size - 1; i < size - 1 + n; i++) dat[i] = v[i - size + 1];
        for (int i = size - 1 + n; i < 2 * size - 1; i++) dat[i] = id;
        dig(0);
    }

    void dig(int v) {
        if (v >= size - 1) return;
        dig(2 * v + 1);
        dig(2 * v + 2);
        dat[v] = f(dat[2 * v + 1], dat[2 * v + 2]);
    }

    ~SegmentTree() {
        delete[] dat;
    }

    void update(int i, const T x) {
        i += size - 1;
        dat[i] = x;
        while (i > 0) {
            i = (i - 1) / 2;
            dat[i] = f(dat[i * 2 + 1], dat[i * 2 + 2]);
        }
    }

    T query(const int a, const int b) const {
        return query_impl(a, b, 0, 0, size);
    }

    T operator[](const int i) const {
        return dat[i + size - 1];
    }
};

int main() {
    int n, m, k, q;
    scanf("%d%d%d%d", &n, &m, &k, &q);
    std::vector<std::vector<std::pair<i64, int>>> g(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        i64 w;
        scanf("%d%d%lld", &u, &v, &w);
        --u; --v;
        g[u].emplace_back(w, v);
        g[v].emplace_back(w, u);
    }

    std::vector<std::tuple<i64, int, int>> edges;
    {
        std::vector<i64> dist(n);
        std::vector<int> nearest(n, -1);
        // (cost, vertex, nearest central)
        std::priority_queue<P, std::vector<P>, std::greater<>> pq;
        for (int i = 0; i < k; i++) pq.emplace(0ll, i, i);

        while (!pq.empty()) {
            auto [d, v, nc] = pq.top();
            pq.pop();
            if (nearest[v] >= 0) continue;
            nearest[v] = nc;
            dist[v] = d;
            for (auto [cost, to] : g[v]) {
                if (nearest[to] < 0) {
                    pq.emplace(d + cost, to, nc);
                } else if (nearest[to] != nearest[v]) {
                    edges.emplace_back(dist[to] + d + cost, nearest[to], nc);
                }
            }
        }
    }

    std::sort(edges.begin(), edges.end());

    std::vector<std::vector<std::pair<i64, int>>> mst(k);
    {
        UF uft(k);
        for (auto [cost, u, v] : edges) {
            if (uft.same(u, v)) continue;
            uft.unite(u, v);
            mst[u].emplace_back(cost, v);
        }
    }

    HLDecomposition hld(k);
    for (int i = 0; i < k; i++) {
        for (auto [cost, j] : mst[i]) hld.add_edge(i, j);
    }
    hld.build();

    std::vector<i64> base(k);
    for (int i = 0; i < k; i++) for (auto [cost, j] : mst[i]) {
        int v = hld.depth[i] > hld.depth[j] ? i : j;
        base[v] = cost;
    }

    auto st = SegmentTree(base, 0ll, [](const auto lhs, const auto rhs) {
        return std::max(lhs, rhs);
    });

    for (int i = 0; i < q; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        i64 ret = 0;
        hld.for_each_edge(a - 1, b - 1, [&](int l, int r) {
            ret = std::max(ret, st.query(l, r));
        });
        printf("%lld\n", ret);
    }

    return 0;
}
