[ZJOI2008]骑士 题解
题目地址:洛谷:【P2607】[ZJOI2008]骑士 – 洛谷、BZOJ:P …
May all the beauty be blessed.
题目地址:BZOJ:Problem 3683. — Falsita
描述
到海边了呢……
如果没有那次选择,现在是不是会好些呢……
都过去了。
仰望着星空,迎面吹过一阵阵海风,倚靠着护栏,Fine 在海边静静地伫立着,在一个个无际的长夜后,Fine 终于放下了往事的痛楚,得到了治愈。
但是作为 Fine 的另一重人格的 Falsita 就没那么幸运了。她仍然被各种繁忙的事务困扰着。
虽然同在一副躯体中,Fine 与 Falsita 的精神世界却相差甚远,Fine 可以轻易地构造出幻梦时,Falsita 却只能停留在现实的痛楚中。
但是为了生活需要,她们还是需要经常达成共识。
让我们形式化的描述一下吧。
她们所在的精神世界是一棵以 1 号节点为根的树,每个树上的节点 u 都有一个权值Wu,她们每个人分别都在一个节点上,达成共识的方法就是两个人都到达一个共识节点(即到达它们的最近公共祖先)。
一个点 u 与另外一个点 v 之间想要达到共识需要花费的代价为Wu+Wv。
有时两人的精神有所触动时,有时点的权值会改变成某个数,有时以某个点的子树中的所有点的权值会加上某个数。
Falsita 和 Fine 经常需要达成共识,每一次询问,已知达成的共识节点,求她们花费的期望代价。
有一棵有点权的树,以1为根。有三种操作:
输入格式:
输入共 m + 3 行。
第一行两个整数 n, m ,表示节点个数和操作数。
第二行 n – 1 个整数Pi,表示节点 i ( i = 2 . . . n ) 的父亲节点的编号。
第三行 n 个整数Wi。
接下来 m 行,每行表示一个操作。
1. S u delta 表示将节点 u 的权值加上 delta 。
2. M u delta 表示将以节点 u 为根的子树中的所有节点的权值加上 delta。
3. Q u 表示询问共识节点为 u 时的答案。
询问保证 u 不是叶子节点。
输出格式:
对于每组询问,输出答案,答案精确到小数点后 6 位。
你的程序输出的答案需要与标准答案之差不超过10^(-5)。
输入样例#1:
4 6 1 2 2 0 -6 3 0 S 2 -5 M 3 8 S 1 -1 M 4 7 M 3 2 Q 1
输出样例#1:
2.000000
前5个操作后,四个节点的权值分别为-1,-11,13,7。最近公共祖先为1的点对有(1,2),(1,3),(1,4),权值和分别为-12,12,6,故答案为(-12+12+6)/3=2。
对于 100% 的数据,1 ≤ n, m ≤ 300000, 0≤ |delta|, |Wi|≤ 20000。
首先,期望可以表示为所有可能情况的和/情况数。对于确定的点$u$,它子树中所有可能情况的数量是可以预处理出来的。这个答案就是$u$子树中不同时在$u$的某个儿子的子树内的点对数,我们可以DFS树的使用利用子树大小来计算,假如计算$u$的方案数,$u$有儿子$v$,在遍历到$v$的时候,只需要对方案数加上$siz[v] \cdot (已经遍历过的子树大小和+1)$即可。
接下来考虑期望中分子的问题。如果不带修改,我们可以DFS的时候计算出所有节点的答案。如果用$sum[u]$表示$u$子树内所有节点的权值和,那么$u$有儿子$v$,$v$子树对$u$的答案贡献是$sum[v] \cdot (siz[u] – siz[v])$。
接下来我们考虑修改的问题,单点修改时,对该点答案的影响是$delta \cdot (siz[u] – 1)$,若该点有祖先$anc$,该点到该祖先路径上,祖先的儿子节点是$anc’$,那么对于该点的该祖先的影响是$delta \cdot (siz[anc] – siz[anc’])$。我们考虑进行树剖,在线段树上来维护这些个影响。显然,我们可以维护一个标记$delta \cdot (siz[u] – siz[v])$,其中$v$是$u$在重链上的儿子,这样,单点修改$u$就可以从$u$的父亲开始跳重链改了。但是当我们发现,一条重链最深的节点的重儿子可能不是修改点到根路径上该点的儿子,因此最深节点的情况要特别处理。我们直接把这个特别处理的修改改到该点处就好了。
对于子树加的情况,我们发现,只需要对子树里所有的节点的分子加上$delta \cdot 分母$就好了。而对于子树根的祖先们,其实跟单点修改产生的影响相同,只不过这里的影响是$delta \cdot siz[u]$。
总之,我们树剖以后在线段树上把这些个标记维护好就能实现正确的复杂度$O(n \log^2 n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline bool isop(char c) {
return c == 'S' || c == 'M' || c == 'Q';
}
inline char readop() {
char c;
while(!isop(c = fgc())) {}
return c;
}
const int MAXN = 300005;
int n, m, w[MAXN];
std::vector<int> gra[MAXN];
int fa[MAXN], siz[MAXN], dep[MAXN], son[MAXN], dfn[MAXN], vn[MAXN], top[MAXN], clk;
LL ans1[MAXN], ans2[MAXN], sum[MAXN];
inline void dfs1(int u) {
siz[u] = 1; sum[u] = w[u];
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == fa[u]) continue;
fa[v] = u; dep[v] = dep[u] + 1;
dfs1(v);
ans2[u] += 1ll * siz[v] * siz[u];
siz[u] += siz[v];
sum[u] += sum[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
inline void dfs2(int u, int tp) {
dfn[u] = ++clk; vn[dfn[u]] = u; top[u] = tp;
ans1[u] += 1ll * w[u] * (siz[u] - 1);
if(son[u]) {
ans1[u] += 1ll * (siz[u] - siz[son[u]]) * sum[son[u]];
dfs2(son[u], tp);
}
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == fa[u] || v == son[u]) continue;
ans1[u] += 1ll * (siz[u] - siz[v]) * sum[v];
dfs2(v, v);
}
}
#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)
LL tag1[MAXN << 2], tag2[MAXN << 2];
// 1: ptadd 2: subtadd
inline void pushdown(int o, int l, int r) {
if(tag1[o]) {
tag1[lch] += tag1[o]; tag1[rch] += tag1[o];
if(l == mid) ans1[vn[l]] += tag1[o] * ans2[vn[l]];
if(mid + 1 == r) ans1[vn[r]] += tag1[o] * ans2[vn[r]];
tag1[o] = 0;
}
if(tag2[o]) {
tag2[lch] += tag2[o]; tag2[rch] += tag2[o];
if(l == mid) ans1[vn[l]] += (siz[vn[l]] - siz[son[vn[l]]]) * tag2[o];
if(mid + 1 == r) ans1[vn[r]] += (siz[vn[r]] - siz[son[vn[r]]]) * tag2[o];
tag2[o] = 0;
}
}
inline void modify(int o, int l, int r, int ll, int rr, LL v, int type) {
if(l >= ll && r <= rr) {
if(type == 1) {
if(l == r) ans1[vn[l]] += v * ans2[vn[l]];
else tag1[o] += v;
} else if(type == 2) {
if(l == r) ans1[vn[l]] += (siz[vn[l]] - siz[son[vn[l]]]) * v;
else tag2[o] += v;
}
return;
}
pushdown(o, l, r);
if(ll <= mid) modify(lch, l, mid, ll, rr, v, type);
if(rr > mid) modify(rch, mid + 1, r, ll, rr, v, type);
}
inline void update(int o, int l, int r, int x) {
if(l == r) return;
pushdown(o, l, r);
if(x <= mid) update(lch, l, mid, x);
else update(rch, mid + 1, r, x);
}
inline void modify(int u, int _lu, LL v) {
int tu = top[u], lu = _lu;
while(u) {
if(dfn[tu] != dfn[u]) modify(1, 1, n, dfn[tu], dfn[u] - 1, v, 2);
ans1[u] += v * (siz[u] - siz[lu]);
lu = u; u = fa[tu]; tu = top[u];
}
}
int main() {
n = readint(); m = readint();
for(int i = 2, p; i <= n; i++) {
p = readint();
gra[i].push_back(p); gra[p].push_back(i);
}
for(int i = 1; i <= n; i++) {
w[i] = readint();
}
dfs1(1);
dfs2(1, 1);
char op; int u, delta;
while(m--) {
op = readop(); u = readint();
if(op != 'Q') delta = readint();
if(op == 'S') {
ans1[u] += 1ll * delta * (siz[u] - 1);
modify(fa[u], u, delta);
} else if(op == 'M') {
modify(1, 1, n, dfn[u], dfn[u] + siz[u] - 1, delta * 2ll, 1);
modify(fa[u], u, 1ll * delta * siz[u]);
} else {
update(1, 1, n, dfn[u]);
printf("%.6lf\n", double(ans1[u]) / ans2[u]);
}
}
return 0;
}
题目地址:洛谷:【P4292】[WC2010]重建计划 – 洛谷、BZOJ:Problem 1758. — [Wc2010]重建计划
给你一棵树,求长度在[L, U]范围内的树链的边权平均值的最大值。
输入格式:
第一行包含一个正整数N,表示X国的城市个数。
第二行包含两个正整数L、U,表示政府要求的第一期重建方案中修建道路数的上下限。
接下来的N-1行描述重建小组的原有方案,每行三个正整数ai, bi, vi,分别表示道路(ai, bi),其价值为vi。其中城市由1…N标号。
输出格式:
仅包含一行,为一个实数AvgValue,即最大平均价值。
小数点后保留三位。
输入样例#1:
4 2 3 1 2 1 1 3 2 1 4 3
输出样例#1:
2.500
新方案中选择路径(3, 1), (1, 4)可以得到的平均价值为2.5,为最大平均价值。
对于20%的数据,N ≤ 5 000;
另有30%的数据,N ≤ 100 000, 原有方案恰好为一条路径(链);
对于100%的数据,N ≤ 100 000, 1 ≤ L ≤ U ≤ N-1, vi ≤ 10^6。
整个树找树链可以考虑使用点分治。
对于每个重心,我们考虑二分答案,验证的时候找两条子树到重心的路径拼起来长度在范围内的,或者一条路径长度就在范围内的,在处理边权和的时候在每个边权上减掉答案,则只需要判断边权和是否不小于0即可。
由于对于以前找到过的每一个深度的链,只需要存下边权和最大值,就可以直接拿来判断了。而枚举每个深度去遍历合法区间,会造成复杂度退化为$O(n^2)$。我们考虑单调队列优化这一过程,从浅到深枚举当前遍历子树的每个节点,对应的在以前找到过的深度区间可以确定为[L-d, R-d],由于遍历的顺序单调,单调队列可以倒着从深到浅维护最大值来判定,遍历节点时使用BFS,这样节点的遍历顺序就是从浅到深单调的了,这样判定的复杂度是$O(n)$的了。
单调队列的复杂度其实应该是$O(之前找到过的最大深度)$的,因此,我们可以对每一棵子树的深度进行排序,从浅的子树开始遍历,可以优化时间,不过我采用了快排,排序复杂度会多一个$\log n$。
另外,本题有一定的卡常数成分,有以下几个卡常的地方可以作为参考:
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline int max(int a, int b) {
return a > b ? a : b;
}
inline double max(double a, double b) {
return a > b ? a : b;
}
const int MAXN = 100005;
const double EPS = 1e-4;
int n, L, U;
double ans;
struct Edge {
int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, int w) {
gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}
bool vis[MAXN];
int siz[MAXN], rt, rtsiz;
inline void findrt(int u, int fa, int tot) {
siz[u] = 1; int msiz = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || v == fa) continue;
findrt(v, u, tot);
siz[u] += siz[v];
msiz = max(msiz, siz[v]);
}
msiz = max(msiz, tot - siz[u]);
if(msiz < rtsiz) {
rt = u; rtsiz = msiz;
}
}
int fa[MAXN], dep[MAXN], que[MAXN], deq[MAXN];
double dis[MAXN];
int tdep[MAXN], subt[MAXN], subw[MAXN], stot;
inline bool cmp(int a, int b) {
return tdep[a] < tdep[b];
}
inline int dfs(int u, int fa, int dep) {
int mxdep = dep;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || v == fa) continue;
mxdep = max(mxdep, dfs(v, u, dep + 1));
}
return mxdep;
}
double mx[MAXN];
inline bool check(int u, double mid) {
int mxdep = 0;
stot = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
subt[++stot] = v;
subw[v] = gra[i].w;
tdep[v] = dfs(v, u, 1);
}
std::sort(subt + 1, subt + stot + 1, cmp);
for(register int i = 1; i <= stot; i++) {
int v = subt[i];
if(vis[v]) continue;
int hd = 0, tl = 1; fa[v] = u; dep[v] = 1; dis[v] = subw[v] - mid; que[0] = v;
while(hd < tl) {
int uu = que[hd++];
for(register int j = head[uu]; ~j; j = gra[j].nxt) {
int vv = gra[j].to;
if(vis[vv] || vv == fa[uu]) continue;
fa[vv] = uu;
dep[vv] = dep[uu] + 1;
dis[vv] = dis[uu] + gra[j].w - mid;
que[tl++] = vv;
}
}
int l = 1, r = 0, now = mxdep;
for(register int j = 0; j < tl; j++) {
int p = que[j];
while(dep[p] + now >= L && now >= 0) {
while(l <= r && mx[deq[r]] < mx[now]) r--;
deq[++r] = now; now--;
}
while(l <= r && dep[p] + deq[l] > U) l++;
if(l <= r && dis[p] + mx[deq[l]] + EPS >= 0) return true;
}
for(register int j = mxdep + 1; j <= dep[que[tl - 1]]; j++) {
mx[j] = -1e9;
}
for(register int j = 0; j < tl; j++) {
int p = que[j];
mx[dep[p]] = max(mx[dep[p]], dis[p]);
}
mxdep = max(mxdep, dep[que[tl - 1]]);
}
return false;
}
int lim;
inline void divide(int u) {
register double l = ans, r = lim, mid;
while(r - l > EPS) {
mid = (l + r) / 2;
if(check(u, mid)) l = mid; else r = mid;
}
ans = l;
vis[u] = true;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || siz[v] < L) continue;
rt = 0; rtsiz = n; findrt(v, u, siz[v]);
divide(rt);
}
}
int main() {
memset(head, -1, sizeof(head));
n = readint(); L = readint(); U = readint();
for(register int i = 1, a, b, c; i < n; i++) {
a = readint(); b = readint(); c = readint();
lim = max(lim, c);
addedge(a, b, c);
}
rt = 0; rtsiz = n; findrt(1, 0, n);
divide(rt);
printf("%.3lf", ans);
return 0;
}
题目地址:洛谷:【P3085】[USACO13OPEN]阴和阳Yin and Yang – 洛谷、BZOJ:Problem 3127. — [Usaco2013 Open]Yin and Yang
Farmer John is planning his morning walk on the farm. The farm is structured like a tree: it has N barns (1 <= N <= 100,000) which are connected by N-1 edges such that he can reach any barn from any other. Farmer John wants to choose a path which starts and ends at two different barns, such that he does not traverse any edge twice. He worries that his path might be a little long, so he also wants to choose another “rest stop” barn located on this path (which is distinct from the start or the end).
Along each edge is a herd of cows, either of the Charcolais (white hair) or the Angus (black hair) variety. Being the wise man that he is, Farmer John wants to balance the forces of yin and yang that weigh upon his walk. To do so, he wishes to choose a path such that he will pass by an equal number of Charcolais herds and Angus herds– both on the way from the start to his rest stop, and on the way from the rest stop to the end.
Farmer John is curious how many different paths he can choose that are “balanced” as described above. Two paths are different only if they consist of different sets of edges; a path should be counted only once even if there are multiple valid “rest stop” locations along the path that make it balanced.
Please help determine the number of paths Farmer John can choose!
给出一棵n个点的树,每条边为黑色或白色。问满足以下条件的路径条数:路径上存在一个不是端点的点,使得两端点到该点的路径上两种颜色的边数都相等。
输入格式:
* Line 1: The integer N.
* Lines 2..N: Three integers a_i, b_i and t_i, representing the two barns that edge i connects. t_i is 0 if the herd along that edge is Charcolais, and 1 if the herd is Angus.
输出格式:
* Line 1: One integer, representing the number of possible paths Farmer John can choose from.
输入样例#1:
7 1 2 0 3 1 1 2 4 0 5 2 0 6 3 1 5 7 1
输出样例#1:
1
There are 7 barns and 6 edges. The edges from 1 to 2, 2 to 4 and 2 to 5 have Charcolais herds along them.
No path of length 2 can have a suitable rest stop on it, so we can only consider paths of length 4. The only path that has a suitable rest stop is 3-1-2-5-7, with a rest stop at 2.
同[BZOJ3697]采药人的路径一题。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 200005, INF = 1e9;
struct Edge {
int to, type, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, int type) {
gra[tot] = Edge {v, type, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, type, head[v]}; head[v] = tot++;
}
int n;
LL ans;
int siz[MAXN], rt, rtsiz;
bool vis[MAXN];
inline void findrt(int u, int f, int tot) {
siz[u] = 1; int mxsiz = 0;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to, t = gra[i].type;
if(v == f || vis[v]) continue;
findrt(v, u, tot);
siz[u] += siz[v];
mxsiz = std::max(mxsiz, siz[v]);
}
mxsiz = std::max(mxsiz, tot - siz[u]);
if(mxsiz < rtsiz) {
rt = u; rtsiz = mxsiz;
}
}
int f[MAXN][2], g[MAXN][2], mxdep, cnt[MAXN];
inline void calsubt(int u, int fa, int d, int dep) {
mxdep = std::max(mxdep, dep);
if(cnt[d]) f[d][1]++; else f[d][0]++;
cnt[d]++;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to, t = gra[i].type;
if(v == fa || vis[v]) continue;
calsubt(v, u, d + gra[i].type, dep + 1);
}
cnt[d]--;
}
inline void work(int u) {
int mx = 0;
g[n][0] = 1;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to, t = gra[i].type;
if(vis[v]) continue;
mxdep = 1; calsubt(v, u, n + t, 1);
mx = std::max(mx, mxdep);
ans += 1ll * (g[n][0] - 1) * f[n][0];
for(int j = -mxdep; j <= mxdep; j++) {
ans += 1ll * g[n - j][1] * f[n + j][1] + 1ll * g[n - j][1] * f[n + j][0]
+ 1ll * g[n - j][0] * f[n + j][1];
}
for(int j = -mxdep; j <= mxdep; j++) {
g[n + j][0] += f[n + j][0];
g[n + j][1] += f[n + j][1];
f[n + j][0] = f[n + j][1] = 0;
}
}
for(int i = -mx; i <= mx; i++) {
g[n + i][0] = g[n + i][1] = 0;
}
}
inline void divide(int u) {
vis[u] = true;
work(u);
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to, t = gra[i].type;
if(vis[v]) continue;
rt = 0; rtsiz = INF; findrt(v, u, siz[v]);
divide(rt);
}
}
int u, v, t;
int main() {
memset(head, -1, sizeof(head));
n = readint();
for(int i = 1; i < n; i++) {
u = readint(); v = readint(); t = readint();
addedge(u, v, t ? t : -1);
}
rt = -1; rtsiz = INF; findrt(1, 0, n);
divide(rt);
printf("%lld", ans);
return 0;
}