《信息学奥赛一本通·高手专项训练》集训 Day 7
线段树
题目
同学们现在分散在数轴上 的整点上,设整点 上的同学数量为 。
还有一个集合位置的候选集合 ,初始为 。
你还要处理 次操作,每次操作,可能发生以下事件:
- 在某个位置的同学增加 个或减少 个。
- 令某个区间 内的整点成为可以选择的整点(将 中不在 内的点加入 )。
- 令某个区间 内的整点成为不能选择的整点(将 中在 内的点删去)。
对于区间限制的操作,不保证操作前该区间的整点是否能选择。操作的影响都是会保留下来的。
每次操作过后,你都需要找到 中的一个整点 作为集合位置,最小化所有同学走到该点的距离总和。形式化地,你需要求
如果有多个满足条件的 ,选择 最小的那个。
题解
如果没有关于 的限制,那么这就是个小学数学题,如果把 个 从小到大排成一列,那么 就是中位数。
换句话说, 就是满足 的 (此为 为奇数的情况,仅作为例子)。
但是加上限制后, 就不一定能取到了,但是能符合要求的 仍然是在 左右最靠近 的可选整点之一,我们可以在线段树上二分来寻找,以左边为例,如果当前区间 的右半边 存在可选的整点,就递归右区间,否则递归左区间。
找到后,把绝对值拆成两部分计算答案,取答案较小的那个即可。
具体来说,我们需要用一个线段树维护以下信息:
- 区间整点的个数和 。
- 区间 的和 。
- 区间 的和 。
- 后面两个操作的区间标记 。
时间复杂度 。
代码
#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;
}
题目
小翔有一个长度为 的排列 。定义一个区间 的价值为:
其中 表示整数 的最大公约数。
现在,小翔给了你一个任务:对于每个正整数 ,你需要计算出有多少对 ,满足 。
题解
我们可以从大到小枚举 ,注意到若 ,则 中至少会出现两个 的倍数,于是我们可以枚举 的倍数,把他们的位置记作 ,特别地,。
那么对于任意一个 满足 且 的区间 可能会对 产生贡献。
另外,注意到这些区间可能会被最大的区间覆盖,所以我们记录一个 ,表示以 未左端点的为贡献价值的区间最长能延伸到哪个位置。显然若区间 的子区间 贡献了价值,那么 也已被贡献了价值。因此, 的所有以 为左端点的子区间均未贡献价值。并且, 显然是单调递增的。
具体来说,我们用线段树维护 数组,对于任意 :
- 查询区间 中 值第一个大于等于 的位置,设其为 。
- 将区间 中的 值全部改为 。
操作完成后,所有 操作前后的差值,即线段树全局和的插值就是对 的贡献。
代码
#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;
}
题目
小奇被困在了一个 的迷宫中,迷宫的某些格子有着障碍,无法通过。出口在小奇的右方,因此小奇每一步只能向上、右、下三个方向行走。
由于某些神秘力量的作用,小奇和出口的位置会不断改变,同时迷宫的构造也有可能改变。
你要做的就是帮助小奇算出对于每种情况,小奇最少要走多少步才能到达出口。如果小奇无论怎样都无法走出迷宫,请输出 -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 和倍增
题目
小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;
}
题目
有 个人来游戏厅玩抽奖游戏。抽奖游戏规则如下:每一轮游戏者投入一个硬币,如果抽奖机转出了 ,则会掉出两个硬币,否则将什么都不会发生。
抽奖机的抽奖结果以 为周期,并且你知道一个周期中每一次的结果。 个人每个人都带着一些钱,他们会从第 个人开始玩,第 个人玩完后是第 个人玩,第 个人玩完后是第 个人玩,第 个人玩完后是第 个人玩,第 个人玩完后是第 个人玩,直到某个人没钱了,他们就会一起离开。
求最早在玩第几次游戏后他们会离开,或者他们会一直在这里玩下去。
题解
显然每个人的抽奖是不会互相影响的,那么我们可以分别求出每个人最多能玩到的次数,取最小值即可。
设 表示一个人从某一周期第 轮开始玩,玩了 次的累计收益, 表示一个人从某一周期第 轮开始玩,玩了 次的最小累计收益。
预处理处 和 ,易知同一个人玩的第 次和第 次是同一轮,所以每 局的收益都相同。因此,利用倍增求出前 次的累计收益 ,和这 次当中累计收益的最小值 ,记第 个人原有的硬币数位 ,那么如果 且 ,则这个人永远不会花完硬币。
如果前 次不会花完硬币,那么假设 是玩前 次后的收益,计算玩前 次后,至少能再玩几组游戏, 次一组,记组数为 ,问题转化,从头开始每 个一组,这个人先玩了 次,再玩了 次,如果玩了 次之后不会离开,那么一定能玩到 次,因为第 次的累计收益是第 次的最小值。
算完 次后,用倍增计算还能玩几次即可。
位移时 一定要多开,否则会 ,但数组不用,因为这题空间限制很小。
代码
#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;
}
题目
T 城是一个旅游城市,具有 个景点和 条道路,所有景点编号为 。每条道路连接这 个景区中的某两个景区,道路是单向通行的。每条道路都有一个长度。
为了方便旅游,每个景点都有一个加油站。第 个景点的加油站的费用为 ,加油量为 。若汽车在第 个景点加油,则需要花费 元钱,之后车的油量将被加至油量上限与 中的较小值。不过如果加油前汽车油量已经不小于 ,则不能在该景点加油。
小 C 准备来到 T 城旅游。他的汽车油量上限为 。旅游开始时,汽车的油量为 。在旅游过程中:
- 当汽车油量大于 时,汽车可以沿从当前景区出发的任意一条道路到达另一个景点(不能只走道路的一部分),汽车油量将减少 ;
- 当汽车在景点 且当前油量小于 时,汽车可以在当前景点加油,加油需花费 元钱,这样汽车油量将变为 。
一次旅游的总花费等于每次加油的花费之和,旅游的总路程等于每次经过道路的长度之和。注意多次在同一景点加油,费用也要计算多次,同样地,多次经过同一条道路,路程也要计算多次。
小 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;
}

浙公网安备 33010602011771号