Ride the Wind and Waves题解
Supor__Shoep · · 题解
这里是出题人题解。本题有较多奇怪做法,这里就介绍一下我初步想到的解法。
下文定义
Subtask 1:
枚举两个点
枚举是
期望得分:
Subtask 2:
枚举每一个点依次算答案。
我们从枚举的点
遍历完整棵树,把所有答案累加到
期望得分:
Subtask 3:
这个做法和正解已经非常接近了!!!!!
剖析一下基环树的结构,我们可以将其理解为一个环以及以环上节点为根的许多棵树形成的集合。为了方便表示,树表示以环上节点为根的树,基环树才表示整个集合。因此尝试分类讨论:
-
环上节点:我们发现任意两个环上节点并不会互相产生代价,也就是说只有树上的某些节点会对环上节点产生代价。考虑预处理出每一棵树中,离根节点至少
k 条边的所有点到根节点的距离之和。设一棵树T 中的距离之和为w_0 ,某个环上节点x 到T 的根节点的距离为w_1 ,则T 带给x 的答案的代价就是w_0\times w_1 。因此环上节点的答案就可以用O(cnt^2) 求了。 -
不同树上的节点:如果两个节点在不同的树中,那么它们两个可能会产生代价。不难发现这里的思路与上一种情况类似,设一棵树为
T ,T 的根节点答案为w_0 ,考虑从根节点向下进行搜索(注意是在反图上面搜索),每次深度增加一个边权val 的时候,只有未翻转的边权之和在发生改变,已翻转的边权之和是不会变的,因此树上节点的答案可以根据其父亲节点的答案推来,并且\Delta=\Delta val\times D ,其中D 表示已翻转的边权之和,是非常好求的。 -
同一棵树上节点:这是一部分人容易忽略的一种情况。我们可以通过枚举
x 来计算x 对其它节点的影响代价。首先我们发现单个枚举x 肯定不行,于是我们将枚举的x 换一个含义,它代表的是以x 为根的子树。我们找到x 向上的k 级祖先y ,如果找不到,则说明x 不会给同一棵树中的节点带来代价,那么整棵子树也就不能带来完整的代价。否则,x\to y 的路径就相当于已翻转的边权之和,如下图所示,k=1 ,红色部分表示枚举的子树,而蓝色的部分就是这棵子树送出代价的范围。现在我们要将
x 的子树中所有节点的答案传到其它节点。设x 的k 级祖先为y ,记R 表示x 中所有节点到y 的距离之和,dis_i 表示i 到根节点的距离,则对于蓝色部分的所有节点p ,答案增加R\times (dis_p-dis_y) ,把式子拆开变成R\times dis_p-R\times dis_y 。这里的dis_p 取自于蓝色部分,而不属于红色部分,考虑用差分维护\sum R 和\sum R\times dis_y 。最后计算答案的时候从根开始,向下将差分值进行累加即可。
计算环上的答案时,我们采用了
期望得分:
Subtask 4:
在
不难发现环上每一个节点的答案之间都有一些联系,考虑设计一个动态规划求解。
设
给一个图:
假如我们枚举的是
根据上图可以发现
由于
差分
期望得分:
标程:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
int head[MAXN], nxt[MAXN], to[MAXN], val[MAXN], tot;
void add(int x, int y, int z) {
to[++tot] = y;
val[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
vector<pair<int, int> > v[MAXN];
int n, k;
void read(int &x) {
x = 0;
short flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= flag;
}
long long D[MAXN];
int vv[MAXN], vis[MAXN], stk[MAXN], cnt;
int dfs1(int x) {//找环
if (vv[x] == 1) {
vv[x] = 2, vis[x] = 1, stk[++cnt] = x;
return 1;
}
vv[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
if (dfs1(to[i])) {
if (vv[x] != 2) {
vis[x] = 1, stk[++cnt] = x;
return 1;
}
return 0;
}
}
return 0;
}
int now, len[MAXN];
void dfs(int x, int dep, long long sum) {
if (dep >= k)
D[now] += sum;//更新 D[i] 的值
for (int i = 0; i < len[x]; i++) {
if (vis[v[x][i].first])
continue;
dfs(v[x][i].first, dep + 1, sum + v[x][i].second);
}
}
long long res[MAXN], dis[MAXN];
int temp[MAXN];
long long Dis[MAXN];
long long sum;
void dfs2(int x) {
for (int i = 0; i < len[x]; i++) {
if (vis[v[x][i].first])
continue;
res[v[x][i].first] = res[x] + sum * v[x][i].second, Dis[v[x][i].first] = Dis[x] + v[x][i].second;
dfs2(v[x][i].first);
}
}
int Get(int x, int dep) {//求 x 的 dep 级祖先
int rsum = dep;
while (rsum--) {
if (vis[x])
return -1;
x = to[head[x]];
}
return x;
}
long long sumup[MAXN], siz[MAXN];//sumup[i] 表示 i 的子树中所有点到 i 的距离之和,siz[i] 表示子树大小
long long cf1[MAXN], cf2[MAXN];//分别维护 R 和 R * dis[y] 的和
void dfs3(int x) {
siz[x] = 1;
for (int i = 0; i < len[x]; i++) {
if (vis[v[x][i].first])
continue;
dfs3(v[x][i].first);
siz[x] += siz[v[x][i].first], sumup[x] += 1ll * siz[v[x][i].first] * v[x][i].second + sumup[v[x][i].first];
}
int ancestor2 = Get(x, k - 1), ancestor = Get(ancestor2, 1);
if (ancestor == -1)
return;
long long rsum = siz[x] * (Dis[x] - Dis[ancestor]) + sumup[x];//求出 R
cf1[ancestor] += rsum, cf1[ancestor2] -= rsum;//差分
cf2[ancestor] += rsum * Dis[ancestor], cf2[ancestor2] -= rsum * Dis[ancestor];
}
void dfs_down(int x) {//向下累加差分值并统计答案
for (int i = 0; i < len[x]; i++) {
if (vis[v[x][i].first])
continue;
cf1[v[x][i].first] += cf1[x], cf2[v[x][i].first] += cf2[x];
dfs_down(v[x][i].first);
}
res[x] += Dis[x] * cf1[x] - cf2[x];
}
int main() {
read(n), read(k);
for (int i = 1; i <= n; i++) {
int x, y, z;
read(x), read(y), read(z);
add(x, y, z), v[y].emplace_back(x, z);
len[y]++;
}
dfs1(1);
for (int i = 2; i <= cnt; i++) temp[i] = stk[i];
for (int i = 2; i <= cnt; i++) stk[i] = temp[cnt - i + 2];//处理完之后,stk[i] 就表示第 i 个环上节点
for (int i = 1; i <= cnt; i++) now = i, dfs(stk[i], 0, 0);//求出 D[i]
for (int i = 2; i <= cnt; i++) dis[i] = dis[i - 1] + val[head[stk[i - 1]]];//预处理前缀和
dis[cnt + 1] = dis[cnt] + val[head[stk[cnt]]];
for (int i = 2; i <= cnt; i++) sum += D[i], res[stk[1]] += dis[i] * D[i];//预处理 D[i] 的和,并暴力枚举第一个点的答案
for (int i = 2; i <= cnt; i++) {
int wx = val[head[stk[i - 1]]];
res[stk[i]] = res[stk[i - 1]] + (dis[cnt + 1] - wx) * D[i - 1] - sum * wx;//转移
sum -= D[i], sum += D[i - 1];
}
sum += D[cnt];
for (int i = 1; i <= cnt; i++) sum -= D[i], dfs2(stk[i]), sum += D[i];//向树上传递答案
for (int i = 1; i <= cnt; i++) dfs3(stk[i]);//处理在同一棵树上的两个节点之间的答案
for (int i = 1; i <= cnt; i++) dfs_down(stk[i]);//统计答案
for (int i = 1; i <= n; i++) printf("%lld\n", res[i]);
return 0;
}