《信息学奥赛一本通·高手专项训练》集训 Day 7
luckydrawbox · · 个人记录
线段树
\color{#9D3DCF}\text{A. 筹备计划}
题目
同学们现在分散在数轴上
还有一个集合位置的候选集合
你还要处理
- 在某个位置的同学增加
x 个或减少x 个。 - 令某个区间
[x,y] 内的整点成为可以选择的整点(将[x,y] 中不在S 内的点加入S )。 - 令某个区间
[x,y] 内的整点成为不能选择的整点(将S 中在[x,y] 内的点删去)。
对于区间限制的操作,不保证操作前该区间的整点是否能选择。操作的影响都是会保留下来的。
每次操作过后,你都需要找到 中的一个整点 作为集合位置,最小化所有同学走到该点的距离总和。形式化地,你需要求
如果有多个满足条件的
题解
如果没有关于
换句话说,
但是加上限制后,
找到后,把绝对值拆成两部分计算答案,取答案较小的那个即可。
具体来说,我们需要用一个线段树维护以下信息:
- 区间整点的个数和
zsum 。 - 区间
a_i 的和sum 。 - 区间
a_i\times i 的和mul 。 - 后面两个操作的区间标记
tag 。
时间复杂度
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5 + 10;
int n, q;
ll a[N];
#define pl p << 1
#define pr p << 1 | 1
#define S_T_T ll
struct Segment_Tree {
struct Tree {
int l, r;
int tag;
S_T_T zsum, sum, mul;
} a[N * 4];
void pushup(int p) {
a[p].zsum = a[pl].zsum + a[pr].zsum;
a[p].sum = a[pl].sum + a[pr].sum;
a[p].mul = a[pl].mul + a[pr].mul;
}
void pushdown(int p) {
if (a[p].tag != -1) {
a[pl].zsum = a[p].tag * (a[pl].r - a[pl].l + 1);
a[pr].zsum = a[p].tag * (a[pr].r - a[pr].l + 1);
a[pl].tag = a[pr].tag = a[p].tag;
a[p].tag = -1;
}
}
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
a[p].tag = -1;
if (l == r) {
a[p].zsum = a[p].sum = a[p].mul = 0;
return;
}
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change1(int p, int l, int r, S_T_T v) {
if (l <= a[p].l && a[p].r <= r) {
a[p].tag = v;
a[p].zsum = v * (a[p].r - a[p].l + 1);
return;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (l <= mid)
change1(pl, l, r, v);
if (r > mid)
change1(pr, l, r, v);
pushup(p);
}
void change2(int p, int x, S_T_T v) {
if (a[p].l == a[p].r) {
a[p].sum += v;
a[p].mul += v * x;
return;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (x <= mid)
change2(pl, x, v);
if (x > mid)
change2(pr, x, v);
pushup(p);
}
S_T_T askzsum(int p, int l, int r) {
if (l <= a[p].l && a[p].r <= r) {
return a[p].zsum;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
S_T_T ans = 0;
if (l <= mid)
ans += askzsum(pl, l, r);
if (r > mid)
ans += askzsum(pr, l, r);
return ans;
}
S_T_T asksum(int p, int l, int r) {
if (l <= a[p].l && a[p].r <= r) {
return a[p].sum;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
S_T_T ans = 0;
if (l <= mid)
ans += asksum(pl, l, r);
if (r > mid)
ans += asksum(pr, l, r);
return ans;
}
S_T_T askmul(int p, int l, int r) {
if (l > r)
return 0;
if (l <= a[p].l && a[p].r <= r) {
return a[p].mul;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
S_T_T ans = 0;
if (l <= mid)
ans += askmul(pl, l, r);
if (r > mid)
ans += askmul(pr, l, r);
return ans;
}
ll query_k(int p, ll ksum) {
if (a[p].l == a[p].r) {
return a[p].l;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (ksum <= a[pl].sum)
return query_k(pl, ksum);
else
return query_k(pr, ksum - a[pl].sum);
}
ll query_l(int p, ll kz) {
if (!kz)
return 0;
if (a[p].l == a[p].r) {
return a[p].l;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (kz <= a[pl].zsum)
return query_l(pl, kz);
else
return query_l(pr, kz - a[pl].zsum);
}
ll query_r(int p, ll kz) {
if (!kz)
return 0;
if (a[p].l == a[p].r) {
return a[p].l;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (kz <= a[pr].zsum)
return query_r(pr, kz);
else
return query_r(pl, kz - a[pr].zsum);
}
} tree;
int main() {
freopen("position.in", "r", stdin);
freopen("position.out", "w", stdout);
n = read();
q = read();
tree.build(1, 1, n);
tree.change1(1, 1, n, 1);
for (int i = 1; i <= n; i++) {
tree.change2(1, i, read());
}
while (q--) {
ll type, x, y;
type = read();
x = read();
y = read();
if (type == 1)
tree.change2(1, x, y);
else if (type == 2)
tree.change2(1, x, -y);
else if (type == 3)
tree.change1(1, x, y, 1);
else if (type == 4)
tree.change1(1, x, y, 0);
ll sum = tree.a[1].sum, ksum, zk, k;
ksum = (sum + 1) / 2;
k = tree.query_k(1, ksum);
zk = tree.askzsum(1, 1, k);
ll L = tree.query_l(1, zk), R = tree.query_r(1, tree.a[1].zsum - zk);
if (!L && !R) {
puts("-1");
continue;
}
if ((!L && R) || (!R && L)) {
write(L + R);
putchar('\n');
continue;
}
ll vl = tree.asksum(1, 1, L) * L - tree.askmul(1, 1, L) + tree.askmul(1, L + 1, n) -
tree.asksum(1, L + 1, n) * L;
ll vr = tree.asksum(1, 1, R) * R - tree.askmul(1, 1, R) + tree.askmul(1, R + 1, n) -
tree.asksum(1, R + 1, n) * R;
if (vl <= vr)
write(L);
else
write(R);
putchar('\n');
}
return 0;
}
\color{#9D3DCF}\text{B. 求公约数}
题目
小翔有一个长度为
其中
现在,小翔给了你一个任务:对于每个正整数
题解
我们可以从大到小枚举
那么对于任意一个
另外,注意到这些区间可能会被最大的区间覆盖,所以我们记录一个
具体来说,我们用线段树维护
- 查询区间
[seq_{i-1}+1,seq_i] 中pos 值第一个大于等于seq_i+1 的位置,设其为l 。 - 将区间
[l,seq_i] 中的pos 值全部改为seq_{i+1}-1 。
操作完成后,所有
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e5 + 10;
int n, a[N], pos[N], len, seq[N];
ll ans[N];
#define pl p << 1
#define pr p << 1 | 1
#define S_T_T int
struct Segment_Tree {
struct Tree {
int l, r;
int tag, mx;
ll sum;
} a[N * 4];
void pushup(int p) {
a[p].sum = a[pl].sum + a[pr].sum;
a[p].mx = max(a[pl].mx, a[pr].mx);
}
void pushdown(int p) {
if (a[p].tag != -1) {
a[pl].sum = 1ll * (a[pl].r - a[pl].l + 1) * a[p].tag;
a[pl].mx = a[p].tag;
a[pl].tag = a[p].tag;
a[pr].sum = 1ll * (a[pr].r - a[pr].l + 1) * a[p].tag;
a[pr].mx = a[p].tag;
a[pr].tag = a[p].tag;
a[p].tag = -1;
}
}
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
a[p].tag = -1;
if (l == r) {
a[p].sum = a[p].mx = n;
return;
}
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change(int p, int l, int r, ll v) {
if (l <= a[p].l && a[p].r <= r) {
a[p].sum = 1ll * (a[p].r - a[p].l + 1) * v;
a[p].mx = a[p].tag = v;
return;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (l <= mid)
change(pl, l, r, v);
if (r > mid)
change(pr, l, r, v);
pushup(p);
}
S_T_T ask(int p, int l, int r, ll v) {
if (a[p].l == a[p].r) {
if (a[p].mx < v || a[p].l > r)
return -1;
if (a[p].l < l)
return l;
return a[p].l;
}
pushdown(p);
if (a[pl].mx >= v)
return ask(pl, l, r, v);
else
return ask(pr, l, r, v);
}
} tree;
int main() {
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
pos[a[i]] = i;
}
tree.build(1, 1, n);
for (int x = n; x >= 1; x--) {
len = 0;
for (int i = x; i <= n; i += x) seq[++len] = pos[i];
sort(seq + 1, seq + len + 1);
ans[x] = tree.a[1].sum;
for (int i = 1; i < len; i++) {
int l = tree.ask(1, seq[i - 1] + 1, seq[i], seq[i + 1]);
if (l != -1)
tree.change(1, l, seq[i], seq[i + 1] - 1);
}
ans[x] -= tree.a[1].sum;
}
for (int i = 1; i <= n; i++) {
write(ans[i]);
putchar('\n');
}
return 0;
}
\color{#9D3DCF}\text{C. 走出迷宫}
题目
小奇被困在了一个
由于某些神秘力量的作用,小奇和出口的位置会不断改变,同时迷宫的构造也有可能改变。
你要做的就是帮助小奇算出对于每种情况,小奇最少要走多少步才能到达出口。如果小奇无论怎样都无法走出迷宫,请输出 -1。
题解
由
对于一个区间
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 6, M = 2.1e5 + 10;
ll n, m, q, c[N][M], inf = 0x3f3f3f3f;
#define pl p << 1
#define pr p << 1 | 1
#define S_T_T ll
struct Segment_Tree {
struct Tree {
int l, r;
S_T_T cx[N][N];
} a[M * 4], ans;
void pushup(int p) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
a[p].cx[i][j] = inf;
for (int k = 1; k <= n; k++)
a[p].cx[i][j] = min(a[p].cx[i][j], a[pl].cx[i][k] + 1 + a[pr].cx[k][j]);
}
}
}
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
if (l == r) {
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++) a[p].cx[i][j] = a[p].cx[j][i] = j - i;
return;
}
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change(int p, int x, int y) {
if (a[p].l == a[p].r) {
if (a[p].cx[x][x] == 0) {
for (int i = 1; i <= x; i++) {
for (int j = x; j <= n; j++) a[p].cx[i][j] = a[p].cx[j][i] = inf;
}
} else {
a[p].cx[x][x] = 0;
for (int i = 1; i < x; i++) {
a[p].cx[i][x] = a[p].cx[x][i] = a[p].cx[i][x - 1] + 1;
for (int j = x + 1; j <= n; j++) {
a[p].cx[i][j] = a[p].cx[j][i] = a[p].cx[i][x - 1] + 2 + a[p].cx[x + 1][j];
}
}
for (int i = x + 1; i <= n; i++) {
a[p].cx[x][i] = a[p].cx[i][x] = 1 + a[p].cx[x + 1][i];
}
}
return;
}
int mid = (a[p].l + a[p].r) >> 1;
if (y <= mid)
change(pl, x, y);
if (y > mid)
change(pr, x, y);
pushup(p);
}
Tree ask(int p, int y1, int y2) {
if (y1 <= a[p].l && a[p].r <= y2) {
return a[p];
}
int mid = (a[p].l + a[p].r) >> 1;
if (y1 <= mid && y2 > mid) {
Tree ans, tl = ask(pl, y1, y2), tr = ask(pr, y1, y2);
ans.l = tl.l;
ans.r = tr.r;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
ans.cx[i][j] = inf;
for (int k = 1; k <= n; k++)
ans.cx[i][j] = min(ans.cx[i][j], tl.cx[i][k] + 1 + tr.cx[k][j]);
}
return ans;
}
if (y1 <= mid)
return ask(pl, y1, y2);
return ask(pr, y1, y2);
}
} tree;
int main() {
freopen("maze.in", "r", stdin);
freopen("maze.out", "w", stdout);
n = read();
m = read();
q = read();
tree.build(1, 1, m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
c[i][j] = read();
if (c[i][j] == 0)
tree.change(1, i, j);
}
}
while (q--) {
int opt, a, b, c, d;
opt = read();
a = read();
b = read();
if (opt == 1) {
tree.change(1, a, b);
} else {
c = read();
d = read();
if (b > d) {
cout << -1 << endl;
continue;
}
tree.ans = tree.ask(1, b, d);
if (tree.ans.cx[a][c] >= inf)
write(-1);
else
write(tree.ans.cx[a][c]);
putchar('\n');
}
}
return 0;
}
LCA 和倍增
\color{#9D3DCF}\text{A. 跑步打卡}
题目
小c 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一一棵包含
现在有
小c 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 小c 想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点
题解
这题就是P1600 [NOIP2016 提高组] 天天爱跑步。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 3e5 + 10;
int n, m, w[N], s[N], t[N], c1[N], c2[2 * N], ans[N];
int head[N], nxt[2 * N], ver[2 * N], tot;
void add(int x, int y) {
ver[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
int fa[N][20], lg[N], dep[N];
struct asdf {
int t, v;
} e;
vector<asdf> d[2][N];
void dfs(int x, int f) {
fa[x][0] = f;
dep[x] = dep[f] + 1;
for (int i = 1; i <= lg[dep[x]]; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for (int i = head[x]; i; i = nxt[i])
if (ver[i] ^ f)
dfs(ver[i], x);
}
void init() {
for (int i = 1, j = 1; i <= n; i++) {
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
dfs(1, 0);
}
int ask_lca(int u, int v) {
if (dep[u] < dep[v])
swap(u, v);
while (dep[u] > dep[v]) u = fa[u][lg[dep[u] - dep[v]] - 1];
if (u == v)
return u;
for (int i = lg[dep[u]] - 1; i >= 0; i--) {
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void Add(int t, int x, int y, int v) {
e.t = y;
e.v = v;
d[t][x].push_back(e);
}
void dfs2(int x, int f) {
int cnt1 = c1[w[x] + dep[x]], cnt2 = c2[w[x] - dep[x] + (int)3e5];
for (int i = 0; i < d[0][x].size(); i++) c1[d[0][x][i].t] += d[0][x][i].v;
for (int i = 0; i < d[1][x].size(); i++) c2[d[1][x][i].t] += d[1][x][i].v;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == f)
continue;
dfs2(y, x);
}
ans[x] = c1[w[x] + dep[x]] - cnt1 + c2[w[x] - dep[x] + (int)3e5] - cnt2;
}
int main() {
freopen("running.in", "r", stdin);
freopen("running.out", "w", stdout);
n = read();
m = read();
for (int i = 1, u, v; i < n; i++) {
u = read();
v = read();
add(u, v);
add(v, u);
}
init();
for (int i = 1; i <= n; i++) {
w[i] = read();
}
for (int i = 1; i <= m; i++) {
s[i] = read();
t[i] = read();
int lca = ask_lca(s[i], t[i]);
Add(0, s[i], dep[s[i]], 1);
Add(0, fa[lca][0], dep[s[i]], -1);
Add(1, t[i], dep[s[i]] - 2 * dep[lca] + 3e5, 1);
Add(1, lca, dep[s[i]] - 2 * dep[lca] + 3e5, -1);
}
dfs2(1, 0);
for (int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
return 0;
}
\color{#9D3DCF}\text{B. 抽奖游戏}
题目
有
抽奖机的抽奖结果以
求最早在玩第几次游戏后他们会离开,或者他们会一直在这里玩下去。
题解
显然每个人的抽奖是不会互相影响的,那么我们可以分别求出每个人最多能玩到的次数,取最小值即可。
设
预处理处
如果前
算完
位移时
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e6 + 10, M = 21;
int n, m;
int a[N], g[N][M], mg[N][M];
ll ans[N], res;
char c[N];
int main() {
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
n = read();
for (int i = 0; i < n; i++) a[i] = read();
m = read();
for (int i = 0; i < m; i++) cin >> c[i];
for (int i = 0; i < m; i++) mg[i][0] = g[i][0] = (c[i] == 'W' ? 1 : -1);
for (int j = 1; j <= 19; j++) {
for (ll i = 0; i < m; i++) {
g[i][j] = g[i][j - 1] + g[(i + ((1ll << (j - 1)) * n)) % m][j - 1];
mg[i][j] = min(mg[i][j - 1], g[i][j - 1] + mg[(i + ((1ll << (j - 1)) * n)) % m][j - 1]);
}
}
for (int i = 0; i < n; i++) {
ll ju = i % m, sum = 0, mx = 0;
for (int j = 19; j >= 0; j--) {
if (m & (1ll << j)) {
mx = min(mx, sum + mg[ju][j]);
sum += g[ju][j];
ju = (ju + (1ll << j) * n % m) % m;
}
}
if (sum >= 0 && a[i] + mx > 0) {
ans[i] = -1;
continue;
}
sum = -sum;
mx = -mx;
if (a[i] > mx) {
ans[i] = 1ll * (a[i] - mx) / sum * m;
a[i] = (a[i] - mx) % sum + mx;
if (a[i] > mx) {
a[i] -= sum;
ans[i] += 1ll * m;
}
}
ju = i % m;
for (int j = 19; j >= 0; j--) {
if (a[i] + mg[ju][j] > 0) {
a[i] += mg[ju][j];
ans[i] += (1ll << j);
ju = (ju + (1ll << j) * n % m) % m;
}
}
ans[i]++;
}
res = -1;
for (int i = 0; i < n; i++) {
if (ans[i] != -1) {
if (res == -1 || res > 1ll * ans[i] * (i + 1) + 1ll * (ans[i] - 1ll) * (n - i - 1))
res = 1ll * ans[i] * (i + 1) + 1ll * (ans[i] - 1ll) * (n - i - 1);
}
}
write(res);
return 0;
}
\color{#9D3DCF}\text{C. 景区旅行}
题目
T 城是一个旅游城市,具有
为了方便旅游,每个景点都有一个加油站。第
小 C 准备来到 T 城旅游。他的汽车油量上限为
- 当汽车油量大于
0 时,汽车可以沿从当前景区出发的任意一条道路到达另一个景点(不能只走道路的一部分),汽车油量将减少1 ; - 当汽车在景点
i 且当前油量小于c_i 时,汽车可以在当前景点加油,加油需花费p_i 元钱,这样汽车油量将变为\min\{c_i,C\} 。
一次旅游的总花费等于每次加油的花费之和,旅游的总路程等于每次经过道路的长度之和。注意多次在同一景点加油,费用也要计算多次,同样地,多次经过同一条道路,路程也要计算多次。
小 C 计划旅游 T 次,每次旅游前,小 C 都指定了该次旅游的起点和目标路程。由于行程不同,每次出发前带的钱也不同。为了省钱,小 C 需要在旅游前先规划好旅游路线(包括旅游的路径和加油的方案),使得从起点出发,按照该旅游路线旅游结束后总路程不小于目标路程,且剩下的钱尽可能多。请你规划最优旅游路线,计算这 T 次旅游每次结束后最多可以剩下多少钱。
题解
设
当
其中
边界:
注意到我们只需要处理出
询问时二分最小的
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 110, M = 1e3 + 110, C = 1e5 + 110;
int n, m, c, t, v[N], x[N];
ll f[N][N * N], w[N][N], g[N][N][18], pre[N][N], mx[N][N * N], edge[M];
int head[N], ver[M], nxt[M], tot;
void add(int x, int y, ll z) {
ver[++tot] = y;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
void init() {
memset(g, -1, sizeof(g));
memset(w, -1, sizeof(w));
memset(f, 128, sizeof(f));
for (int i = 1; i <= n; i++) g[i][i][0] = 0;
for (int xx = 1; xx <= n; xx++)
for (int i = head[xx]; i; i = nxt[i]) {
int y = ver[i];
g[xx][y][0] = max(g[xx][y][0], edge[i]);
}
for (int i = 1; i <= 17; i++)
for (int xx = 1; xx <= n; xx++)
for (int y = 1; y <= n; y++) {
g[xx][y][i] = g[xx][y][i - 1];
for (int k = 1; k <= n; k++)
if (g[xx][k][i - 1] != -1 && g[k][y][i - 1] != -1)
g[xx][y][i] = max(g[xx][y][i], g[xx][k][i - 1] + g[k][y][i - 1]);
}
for (int p = 1; p <= n; p++) {
int sum = x[p];
w[p][p] = 0;
for (int i = 17; i >= 0; i--)
if (sum >= (1 << i)) {
sum -= (1 << i);
for (int u = 1; u <= n; u++) pre[p][u] = w[p][u];
for (int u = 1; u <= n; u++)
for (int v = 1; v <= n; v++)
if (pre[p][u] != -1 && g[u][v][i] != -1)
w[p][v] = max(w[p][v], pre[p][u] + g[u][v][i]);
}
}
for (int i = 0; i <= n * n; i++)
for (int u = 1; u <= n; u++) {
if (v[u] > i) {
f[u][i] = 0;
continue;
}
for (int v_ = 1; v_ <= n; v_++)
if (w[u][v_] != -1)
f[u][i] = max(f[u][i], f[v_][i - v[u]] + w[u][v_]);
}
for (int u = 1; u <= n; u++)
for (int i = 1; i <= n * n; i++) mx[u][i] = max(f[u][i], mx[u][i - 1]);
}
int main() {
freopen("trip.in", "r", stdin);
freopen("trip.out", "w", stdout);
n = read();
m = read();
c = read();
t = read();
for (int i = 1; i <= n; i++) {
v[i] = read();
x[i] = min(read(), (ll)c);
}
for (int i = 1; i <= m; i++) {
int u, v;
ll z;
u = read();
v = read();
z = read();
add(u, v, z);
}
init();
for (int i = 1; i <= t; i++) {
ll s, q, d, l = 0, r;
s = read();
r = q = read();
d = read();
if (mx[s][q] < d) {
puts("-1");
continue;
}
while (l < r) {
ll mid = (l + r) >> 1;
if (mx[s][mid] >= d)
r = mid;
else
l = mid + 1;
}
write(q - l);
putchar('\n');
}
return 0;
}