P2680 [NOIP 2015 提高组] 运输计划
题目背景
NOIP2015 Day2T3
题目描述
公元 2044 年,人类进入了宇宙纪元。
L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。
为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
输入格式
第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。
接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。
接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj 号星球。
输出格式
一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。
输入输出样例
输入 #1复制
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
输出 #1复制
11
说明/提示
所有测试数据的范围和特点如下表所示
| 测试点编号 | n= | m= | 约定 |
|---|---|---|---|
| 1 | 100 | 1 | |
| 2 | 100 | 100 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 3 | 100 | 100 | |
| 4 | 2000 | 1 | |
| 5 | 1000 | 1000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 6 | 2000 | 2000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 7 | 3000 | 3000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 8 | 1000 | 1000 | |
| 9 | 2000 | 2000 | |
| 10 | 3000 | 3000 | |
| 11 | 80000 | 1 | |
| 12 | 100000 | 1 | |
| 13 | 70000 | 70000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 14 | 80000 | 80000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 15 | 90000 | 90000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 16 | 100000 | 100000 | 第 i 条航道连接 i 号星球与 i+1 号星球 |
| 17 | 80000 | 80000 | |
| 18 | 90000 | 90000 | |
| 19 | 100000 | 100000 | |
| 20 | 300000 | 300000 | |
| 所有数据 | 1≤ai,bi,uj,vj≤n,0≤ti≤1000 |
请注意常数因子带来的程序效率上的影响。
实现代码:
cpp
#pragma warning (disable:4996)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define RG register
#define mid ((x+y)>>1)
#define lson (pst<<1)
#define rson (pst<<1|1)
using namespace std;
const int maxn = 3e5 + 5, maxm = maxn << 1, inf = 0x7fffffff;
int x[maxn], y[maxn], z[maxn], p[maxn];
int head[maxm], nxt[maxm], v[maxm], cnt;
int son[maxn], dad[maxn], sz[maxn], depth[maxn], root;
int id[maxn], top[maxn], rak[maxn], num;
int c[maxn], d[maxn], srt[maxn];
int ma, mb, mc;
int n, m;
struct Binary_Indexed_Tree
{
int a[maxn];
inline int lowbit(int k) { return k & (-k); }
inline void update(int x, int k) { for (int i = x; i <= n; i += lowbit(i)) a[i] += k; }
inline int query(int x) { int i = x, ans = 0; for (i = x; i >= 1; i -= lowbit(i)) ans += a[i]; return ans; }
inline void build(int x) { for (int i = 1; i <= n; i++) update(i, p[rak[i]]); }
inline int sum(int l, int r) { return query(r) - query(l - 1); }
}BIT;
inline int max(int x, int y) { return x > y ? x : y; }
inline int min(int x, int y) { return x < y ? x : y; }
struct Segment_Tree
{
int mx[maxn << 2], tag[maxn << 2];
inline void pushdown(int pst)
{
if (!tag[pst]) return;
int k = tag[pst];
mx[lson] = max(mx[lson], k), mx[rson] = max(mx[rson], k);
tag[lson] = max(tag[lson], k), tag[rson] = max(tag[rson], k);
tag[pst] = 0; return;
}
inline void pushup(int pst) { mx[pst] = max(mx[lson], mx[rson]); }
inline void update(int x, int y, int pst, int l, int r, int k)
{
if (x > y || y<l || x>r) return;
if (l <= x && y <= r) { mx[pst] = max(mx[pst], k), tag[pst] = max(tag[pst], k); return; }
pushdown(pst);
update(x, mid, lson, l, r, k), update(mid + 1, y, rson, l, r, k);
pushup(pst); return;
}
inline int query(int x, int y, int pst, int p)
{
if (x == y) return mx[pst];
pushdown(pst);
if (p <= mid) return query(x, mid, lson, p);
else return query(mid + 1, y, rson, p);
}
}ST;
inline void addline(int x, int y) { v[cnt] = y, nxt[cnt] = head[x], head[x] = cnt++; }
inline int read()
{
RG char c = getchar(); RG int x = 0;
while (c<'0' || c>'9') c = getchar();
while (c >= '0'&&c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
inline void dfs1(int x, int f, int d)
{
dad[x] = f, depth[x] = d, sz[x] = 1;
for (RG int i = head[x]; ~i; i = nxt[i])
{
if (v[i] == f) continue;
dfs1(v[i], x, d + 1);
sz[x] += sz[v[i]];
if (sz[v[i]] > sz[son[x]]) son[x] = v[i];
}
return;
}
inline void dfs2(int x, int t)
{
top[x] = t, id[x] = ++num, rak[id[x]] = x;
if (!son[x]) return;
dfs2(son[x], t);
for (RG int i = head[x]; ~i; i = nxt[i])
if (v[i] != dad[x] && v[i] != son[x]) dfs2(v[i], v[i]);
return;
}
inline int sum(int x, int y)
{
RG int tx = top[x], ty = top[y], ans = 0;
while (tx != ty)
{
if (depth[tx] >= depth[ty]) ans += BIT.sum(id[tx], id[x]), x = dad[tx], tx = top[x];
else ans += BIT.sum(id[ty], id[y]), y = dad[ty], ty = top[y];
}
if (id[x] <= id[y]) ans += BIT.sum(id[x] + 1, id[y]);
else ans += BIT.sum(id[y] + 1, id[x]);
return ans;
}
inline bool cmp(int x, int y) { return c[x] < c[y]; }
inline void update(int x, int y, int z)
{
RG int tx = top[x], ty = top[y], t = 0;
while (tx != ty)
{
if (depth[tx] >= depth[ty]) c[++t] = id[tx], d[t] = id[x], x = dad[tx], tx = top[x];
else c[++t] = id[ty], d[t] = id[y], y = dad[ty], ty = top[y];
}
if (id[x] <= id[y]) c[++t] = id[x] + 1, d[t] = id[y];
else c[++t] = id[y] + 1, d[t] = id[x];
for (int i = 1; i <= t; i++) srt[i] = i;
sort(srt + 1, srt + t + 1, cmp);
if (c[srt[1]] > 1) ST.update(1, n, 1, 1, c[srt[1]] - 1, z);
if (d[srt[t]] < n) ST.update(1, n, 1, d[srt[t]] + 1, n, z);
for (int i = 1; i < t; i++) ST.update(1, n, 1, d[srt[i]] + 1, c[srt[i + 1]] - 1, z);
return;
}
inline int find_ans(int x, int y)
{
RG int ans = inf;
if (x == y) return 0;
if (depth[x] < depth[y]) swap(x, y);
while (depth[x] != depth[y]) ans = min(ans, max(mc - p[x], ST.query(1, n, 1, id[x]))), x = dad[x];
while (x != y)
{
if (depth[x] > depth[y]) ans = min(ans, max(mc - p[x], ST.query(1, n, 1, id[x]))), x = dad[x];
else ans = min(ans, max(mc - p[y], ST.query(1, n, 1, id[y]))), y = dad[y];
}
return ans;
}
int main(void)
{
memset(head, -1, sizeof(head));
n = read(), m = read();
for (int i = 1; i < n; i++) x[i] = read(), y[i] = read(), z[i] = read();
for (int i = 1; i < n; i++) addline(x[i], y[i]), addline(y[i], x[i]);
root = rand() % n + 1, dfs1(root, 0, 1), dfs2(root, root);
for (int i = 1; i < n; i++)
{
if (depth[x[i]] > depth[y[i]]) p[x[i]] = z[i];
else p[y[i]] = z[i];
}
BIT.build(n);
for (int i = 1; i <= m; i++)
{
RG int a = read(), b = read(), temp;
temp = sum(a, b), update(a, b, temp);
if (temp >= mc) ma = a, mb = b, mc = temp;
}
printf("%d\n", find_ans(ma, mb));
return 0;
}
P1600 [NOIP 2016 提高组] 天天爱跑步
题目背景
NOIP2016 提高组 D1T2
题目描述
小 C 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含 n 个结点和 n−1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 1 到 n 的连续正整数。
现在有 m 个玩家,第 i 个玩家的起点为 si,终点为 ti。每天打卡任务开始时,所有玩家在第 0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)
小 C 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 j 的观察员会选择在第 wj 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 wj 秒也正好到达了结点 j。小 C 想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。即对于把结点 j 作为终点的玩家:若他在第 wj 秒前到达终点,则在结点 j 的观察员不能观察到该玩家;若他正好在第 wj 秒到达终点,则在结点 j 的观察员可以观察到这个玩家。
输入格式
第一行有两个整数 n 和 m。其中 n 代表树的结点数量,同时也是观察员的数量,m 代表玩家的数量。
接下来 n−1 行每行两个整数 u 和 v,表示结点 u 到结点 v 有一条边。
接下来一行 n 个整数,其中第 j 个整数为 wj,表示结点 j 出现观察员的时间。
接下来 m 行,每行两个整数 si,和 ti,表示一个玩家的起点和终点。
对于所有的数据,保证 1≤si,ti≤n,0≤wj≤n。
输出格式
输出 1 行 n 个整数,第 j 个整数表示结点 j 的观察员可以观察到多少人。
输入输出样例
输入 #1复制
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
输出 #1复制
2 0 0 1 1 1
输入 #2复制
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5
输出 #2复制
1 2 1 0 1
说明/提示
样例 1 说明
对于 1 号点,wi=0,故只有起点为 1 号点的玩家才会被观察到,所以玩家 1 和玩家 2 被观察到,共有 2 人被观察到。
对于 2 号点,没有玩家在第 2 秒时在此结点,共 0 人被观察到。
对于 3 号点,没有玩家在第 5 秒时在此结点,共 0 人被观察到。
对于 4 号点,玩家 1 被观察到,共 1 人被观察到。
对于 5 号点,玩家 1 被观察到,共 1 人被观察到。
对于 6 号点,玩家 3 被观察到,共 1 人被观察到。
子任务
每个测试点的数据规模及特点如下表所示。
提示:数据范围的个位上的数字可以帮助判断是哪一种数据类型。
| 测试点编号 | n= | m= | 约定 |
|---|---|---|---|
| 1∼2 | 991 | 991 | 所有人的起点等于自己的终点,即 ∀i, si=ti |
| 3∼4 | 992 | 992 | 所有 wj=0 |
| 5 | 993 | 993 | 无 |
| 6∼8 | 99994 | 99994 | ∀i∈[1,n−1],i 与 i+1 有边。即树退化成 1,2,...,n 按顺序连接的链 |
| 9∼12 | 99995 | 99995 | 所有 si=1 |
| 13∼16 | 99996 | 99996 | 所有 ti=1 |
| 17∼19 | 99997 | 99997 | 无 |
| 20 | 299998 | 299998 | 无 |
提示
(提示:由于原提示年代久远,不一定能完全反映现在的情况,现在已经对该提示做出了一定的修改,提示的原文可以在该剪贴板查看)
在最终评测时,调用栈占用的空间大小不会有单独的限制,但在我们的工作环境中默认会有 1MiB 的限制。 这可能会引起函数调用层数较多时,程序发生栈溢出崩溃 ,程序中较深层数的递归往往会导致这个问题。如果你的程序需要用到较大的栈空间,请务必注意该问题。
我们可以使用一些方法修改调用栈的大小限制。
- Linux
我们可以在终端中输入下列命令:ulimit -s 1048576。此命令的意义是,将调用栈的大小限制修改为 1048576KiB=1GiB。
例如,对于如下程序 sample.cpp:
#include <bits/stdc++.h>
using namespace std;
int f[1000005];
void dfs(int a){
if(a == 0){
f[a] = 0;
return;
}
dfs(a - 1);
f[a] = f[a - 1] + 1;
}
int main(){
dfs(1000000);
return 0;
}
将上述源代码用命令 g++ sample.cpp -o sample 编译为可执行文件 sample 后,使用 ./sample 执行程序。
如果在没有使用命令 ulimit -s 1048576 的情况下运行该程序,sample 会因为栈溢出而崩溃;如果使用了上述命令后运行该程序,该程序则不会崩溃。
特别地,当你打开多个终端时,它们并不会共享该命令,你需要分别对它们运行该命令。
请注意,调用栈占用的空间会计入总空间占用中,和程序其他部分占用的内存共同受到内存限制。
- Windows
如果你使用 Windows 下的 Dev-C++,请选择 工具-编译选项 并在如下区域填入以下命令 -Wl,--stack=1073741824,填入后注意确认"编译时加入以下命令的"的框是已勾选状态。
此处 1073741824 的单位是 B/Bytes。


实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int SIZE=300000;
int n, m, tot, h[SIZE], deep[SIZE], fa[SIZE][20], w[SIZE];
struct edge
{
int to, next;
}E[SIZE*2], e1[SIZE*2], e2[SIZE*2];
void add(int x, int y)
{
E[++tot].to=y;
E[tot].next=h[x];
h[x]=tot;
}
int tot1, tot2, h1[SIZE], h2[SIZE];
void add1(int x, int y)
{
e1[++tot1].to=y;
e1[tot1].next=h1[x];
h1[x]=tot1;
}
void add2(int x, int y)
{
e2[++tot2].to=y;
e2[tot2].next=h2[x];
h2[x]=tot2;
}
void dfs1(int x)
{
for(int i=1; (1<<i)<=deep[x]; i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=h[x]; i; i=E[i].next)
{
int y=E[i].to;
if(y==fa[x][0]) continue;
fa[y][0]=x;
deep[y]=deep[x]+1;
dfs1(y);
}
}
int get_lca(int x, int y)
{
if(x==y) return x;
if(deep[x]<deep[y]) swap(x, y);
int t=log(deep[x]-deep[y])/log(2);
for(int i=t; i>=0; i--)
{
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y)
return x;
}
t=log(deep[x])/log(2);
for(int i=t; i>=0; i--)
{
if(fa[x][i]!=fa[y][i])
x=fa[x][i], y=fa[y][i];
}
return fa[x][0];
}
int b1[SIZE*2], b2[SIZE*2], js[SIZE], dist[SIZE], s[SIZE], t[SIZE], l[SIZE], ans[SIZE];
void dfs2(int x)
{
int t1=b1[w[x]+deep[x]], t2=b2[w[x]-deep[x]+SIZE];
for(int i=h[x]; i; i=E[i].next)
{
int y=E[i].to;
if(y==fa[x][0]) continue;
dfs2(y);
}
b1[deep[x]]+=js[x];
for(int i=h1[x]; i; i=e1[i].next)
{
int y=e1[i].to;
b2[dist[y]-deep[t[y]]+SIZE]++;
}
ans[x]+=b1[w[x]+deep[x]]-t1+b2[w[x]-deep[x]+SIZE]-t2;
for(int i=h2[x]; i; i=e2[i].next)
{
int y=e2[i].to;
b1[deep[s[y]]]--;
b2[dist[y]-deep[t[y]]+SIZE]--;
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
deep[1]=1;
fa[1][0]=1;
dfs1(1);
for(int i=1; i<=n; i++) scanf("%d", &w[i]);
for(int i=1; i<=m; i++)
{
scanf("%d%d", &s[i], &t[i]);
int lca=get_lca(s[i], t[i]);
dist[i]=deep[s[i]]+deep[t[i]]-2*deep[lca];
js[s[i]]++;
add1(t[i], i);
add2(lca, i);
if(deep[lca]+w[lca]==deep[s[i]]) ans[lca]--;
}
dfs2(1);
for(int i=1; i<=n; i++) printf("%d ", ans[i]);
return 0;
}
P3178 [HAOI2015] 树上操作
题目描述
有一棵点数为 N 的树,以点 1 为根,且树有点权。然后有 M 个操作,分为三种:
- 操作 1:把某个节点 x 的点权增加 a。
- 操作 2:把某个节点 x 为根的子树中所有点的点权都增加 a。
- 操作 3:询问某个节点 x 到根的路径中所有点的点权和。
输入格式
第一行包含两个整数 N,M。表示点数和操作数。
接下来一行 N 个整数,表示树中节点的初始权值。
接下来 N−1 行每行两个正整数 from,to, 表示该树中存在一条边 (from,to)。
再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类,之后接这个操作的参数。
输出格式
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。
输入输出样例
输入 #1复制
5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3
输出 #1复制
6
9
13
说明/提示
对于 100% 的数据,1≤N,M≤105,且所有输入数据的绝对值都不会超过 106。
实现代码:
cpp
#include <cstdio>
#include <cstring>
#define LL long long
#define root 1, 1, n
#define ls now << 1, l, mid
#define rs now << 1 | 1, mid + 1, r
using namespace std;
const int MAXN = 100001;
int n, m, cnt, tot;
int head[MAXN], next[MAXN << 1], to[MAXN << 1], tid[MAXN], size[MAXN];
LL a[MAXN << 2], b[MAXN << 2], val[MAXN], dis[MAXN];
inline void add(int x, int y)
{
to[cnt] = y;
next[cnt] = head[x];
head[x] = cnt++;
}
inline void dfs(int u)
{
int i, v;
tid[u] = ++tot;
size[u] = 1;
for(i = head[u]; i != -1; i = next[i])
{
v = to[i];
if(!size[v])
{
dis[v] = dis[u] + 1;
dfs(v);
size[u] += size[v];
}
}
}
inline void push_down(int now)
{
a[now << 1] += a[now];
a[now << 1 | 1] += a[now];
b[now << 1] += b[now];
b[now << 1 | 1] += b[now];
a[now] = b[now] = 0;
}
inline void update(LL x, LL y, int ql, int qr, int now, int l, int r)
{
if(ql <= l && r <= qr)
{
a[now] += x;
b[now] += y;
return;
}
push_down(now);
int mid = (l + r) >> 1;
if(ql <= mid) update(x, y, ql, qr, ls);
if(mid < qr) update(x, y, ql, qr, rs);
}
inline LL query(int u, int x, int now, int l, int r)
{
if(l == r) return dis[u] * a[now] + b[now];
push_down(now);
int mid = (l + r) >> 1;
if(x <= mid) return query(u, x, ls);
else return query(u, x, rs);
}
int main()
{
int i, x, z;
LL y;
scanf("%d %d", &n, &m);
for(i = 1; i <= n; i++) scanf("%lld", &val[i]);
memset(head, -1, sizeof(head));
for(i = 1; i < n; i++)
{
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
dis[1] = 1;
dfs(1);
for(i = 1; i <= n; i++) update(0, val[i], tid[i], tid[i] + size[i] - 1, root);
for(i = 1; i <= m; i++)
{
scanf("%d %d", &z, &x);
if(z == 1)
{
scanf("%lld", &y);
update(0, y, tid[x], tid[x] + size[x] - 1, root);
}
else if(z == 2)
{
scanf("%lld", &y);
update(y, -((dis[x] - 1) * y), tid[x], tid[x] + size[x] - 1, root);
}
else printf("%lld\n", query(x, tid[x], root));
}
return 0;
}