#include <bits/stdc++.h>
using namespace std;
const int MX = 3e5 + 99;
namespace DSU {
int parent[MX];
int rank[MX];
void make_set(int v) {
  parent[v] = v;
  rank[v] = 0;
}
void init(int K) {
  for (int i = 1; i <= K; i++) make_set(i);
}
int find_set(int v) {
  if (v == parent[v]) return v;
  return parent[v] = find_set(parent[v]);
}
bool same_set(int a, int b) {
  a = find_set(a);
  b = find_set(b);
  return a == b;
}
void union_sets(int a, int b) {
  a = find_set(a);
  b = find_set(b);
  if (a != b) {
    if (rank[a] < rank[b]) swap(a, b);
    parent[b] = a;
    if (rank[a] == rank[b]) ++rank[a];
  }
}
}  // namespace DSU
struct edge {
  int from, to;
  long long w;
  long long w_curr;
};
bool operator<(const edge& A, const edge& B) { return A.w > B.w; }
struct cmp {
  bool operator()(const pair<int, long long>& A,
                  const pair<int, long long>& B) {
    return A.second > B.first;
  }
};
int n, m, k, q;
vector<pair<int, long long> > e[MX];
vector<pair<int, long long> > edge_tree[MX];
priority_queue<edge, vector<edge> > Q;
int closest_origin[MX];
long long dist_to_origin[MX];
const long long INF = (long long)1e16;
void add_neighbors(int from) {
  long long curr_W = dist_to_origin[from];
  for (pair<int, long long> next : e[from]) {
    Q.push({from, next.first, curr_W + next.second, next.second});
  }
}
void add_edge(int A, int B, long long W) {
  ;
  ;
  ;
  ;
  edge_tree[A].push_back({B, W});
  edge_tree[B].push_back({A, W});
}
void do_dijkstra() {
  for (int i = 1; i <= n; i++) {
    closest_origin[i] = -1;
    dist_to_origin[i] = INF;
  }
  for (int from = 1; from <= k; from++) {
    closest_origin[from] = from;
    dist_to_origin[from] = 0;
    add_neighbors(from);
  }
  DSU::init(n);
  while (!Q.empty()) {
    auto curr = Q.top();
    Q.pop();
    int from = curr.from, to = curr.to;
    long long curr_w = curr.w_curr;
    ;
    ;
    ;
    if (DSU::same_set(from, to)) {
      long long A = closest_origin[from], B = closest_origin[to];
      assert(A != -1 && B != -1);
      long long W123 = dist_to_origin[from] + curr_w + dist_to_origin[to];
      add_edge(A, B, W123);
      continue;
    }
    if (closest_origin[to] == -1) {
      closest_origin[to] = closest_origin[from];
      dist_to_origin[to] = dist_to_origin[from] + curr_w;
      add_neighbors(to);
      DSU::union_sets(from, to);
    } else {
      long long A = closest_origin[from], B = closest_origin[to];
      assert(A != -1 && B != -1);
      long long W123 = dist_to_origin[from] + curr_w + dist_to_origin[to];
      add_edge(A, B, W123);
      DSU::union_sets(from, to);
    }
  }
}
vector<bool> visited;
priority_queue<edge, vector<edge> > Q_MST;
void add_neighbors_MST(int from) {
  for (pair<int, long long> next : edge_tree[from]) {
    Q_MST.push({from, next.first, next.second, 0});
  }
}
vector<pair<int, long long> > edge_tree_MST[MX];
void do_MST() {
  add_neighbors_MST(1);
  visited = vector<bool>(k + 1, false);
  visited[1] = true;
  while (!Q_MST.empty()) {
    edge curr = Q_MST.top();
    Q_MST.pop();
    int from = curr.from, to = curr.to;
    long long w = curr.w;
    ;
    ;
    ;
    ;
    if (visited[to]) continue;
    visited[to] = true;
    edge_tree_MST[from].push_back({to, w});
    edge_tree_MST[to].push_back({from, w});
    add_neighbors_MST(to);
  }
  for (int i = 1; i <= k; i++) edge_tree[i] = edge_tree_MST[i];
}
const int MAXN = 1E5 + 99;
namespace LCA {
int n;
int l;
vector<int> tin, tout;
int timer;
vector<vector<int> > up;
vector<vector<long long> > max_val;
void dfs(int v, int p, long long W) {
  tin[v] = ++timer;
  up[v][0] = p;
  max_val[v][0] = W;
  for (int i = 1; i <= l; ++i) up[v][i] = up[up[v][i - 1]][i - 1];
  for (size_t i = 0; i < edge_tree[v].size(); ++i) {
    int to = edge_tree[v][i].first;
    long long new_W = edge_tree[v][i].second;
    if (to != p) dfs(to, v, new_W);
  }
  tout[v] = ++timer;
}
bool upper(int a, int b) { return tin[a] <= tin[b] && tout[a] >= tout[b]; }
int lca(int a, int b) {
  if (upper(a, b)) return a;
  if (upper(b, a)) return b;
  for (int i = l; i >= 0; --i)
    if (!upper(up[a][i], b)) a = up[a][i];
  return up[a][0];
}
long long mxx(long long A, long long B) {
  if (A == B) return 0;
  long long result = 0;
  for (int i = l; i >= 0; --i) {
    if (!upper(up[A][i], B)) {
      result = max(result, max_val[A][i]);
      A = up[A][i];
    }
  }
  return max(result, max_val[A][0]);
}
long long max_on_path(long long A, long long B) {
  int C = lca(A, B);
  return max(mxx(A, C), mxx(B, C));
}
void build_lca(int _n) {
  n = _n;
  tin.resize(n + 1), tout.resize(n + 1), up.resize(n + 1),
      max_val.resize(n + 1);
  l = 1;
  while ((1 << l) <= n) ++l;
  for (int i = 1; i <= n; ++i) {
    up[i].resize(l + 1);
    max_val[i].resize(l + 1);
  }
  dfs(1, 1, 0);
}
}  // namespace LCA
long long result[MX];
int a[MX], b[MX];
void solve() {
  do_dijkstra();
  ;
  do_MST();
  ;
  LCA::build_lca(k);
  for (int i = 0; i < q; i++) {
    result[i] = LCA::max_on_path(a[i], b[i]);
  }
}
int main() {
  cin >> n >> m >> k >> q;
  for (int t = 0; t < m; t++) {
    int u, v;
    long long w;
    cin >> u >> v >> w;
    e[u].push_back({v, w});
    e[v].push_back({u, w});
  }
  for (int i = 0; i < q; i++) {
    cin >> a[i] >> b[i];
  }
  solve();
  for (int i = 0; i < q; i++) cout << result[i] << '\n';
  return 0;
}
