题目大意

给你一棵树, 每个点有一个点权.

有两种操作:

  • link / cut
  • 修改某个点的点权

每次操作后, 你要输出以下答案: 在整棵树中任意选两个点, 这两个点的LCA的期望权值.

Solution

我们考虑每个点作为LCA的概率:

\[P(u为LCA) = \frac{sz[u]^2 - \sum_{v为u的子节点} sz[v]^2}{n^2}
\]

所以我们的答案为

\[\begin{aligned}
E &= \frac{\sum_{每个节点u} (sz[u]^2 - \sum_{v为u的字节点} sz[v]^2) a[u]}{n^2} \\ &= \frac{\sum_{每个节点u} (a[u] - a[fa[u]]) sz[u]^2}{n^2}
\end{aligned}
\]

考虑每次操作的改变量, 把平方拆开维护即可.

link-cut tree真的非常不熟练啊!!!!!

#include <cstdio>
#include <cctype>
#include <set> using namespace std;
namespace Zeonfai
{
inline int getInt()
{
int a = 0, sgn = 1; char c;
while(! isdigit(c = getchar())) if(c == '-') sgn *= -1;
while(isdigit(c)) a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
}
const int N = (int)1e5;
int n;
double ans;
inline long long sqr(int a) {return (long long)a * a;}
struct linkCutTree
{
struct node
{
int suc[2], pre, isRoot;
int a;
int lst; set<int> bck;
int sz, tg;
long long differenceSum, productSum;
inline node()
{
for(int i = 0; i < 2; ++ i) suc[i] = -1; pre = -1; isRoot = 1;
a = 0;
lst = -1; bck.clear();
sz = 1; tg = 0; // 维护原树中的size
differenceSum = productSum = 0; // 重链上的和
}
}nd[N + 1];
inline void pushDown(int u)
{
if(! nd[u].isRoot) pushDown(nd[u].pre);
for(int i = 0; i < 2; ++ i) if(~ nd[u].suc[i])
{
nd[nd[u].suc[i]].tg += nd[u].tg; nd[nd[u].suc[i]].sz += nd[u].tg;
nd[nd[u].suc[i]].productSum += nd[u].tg * nd[nd[u].suc[i]].differenceSum;
}
nd[u].tg = 0;
}
inline int getRelation(int u) {return nd[u].isRoot ? - 1 : u == nd[nd[u].pre].suc[1];}
inline void update(int u)
{
nd[u].differenceSum = nd[u].a - (~ nd[u].lst ? nd[nd[u].lst].a : 0);
nd[u].productSum = nd[u].sz * nd[u].differenceSum;
for(int i = 0; i < 2; ++ i) if(~ nd[u].suc[i])
nd[u].differenceSum += nd[nd[u].suc[i]].differenceSum,
nd[u].productSum += nd[nd[u].suc[i]].productSum;
}
inline void rotate(int u)
{
int pre = nd[u].pre, prepre = nd[pre].pre, k = getRelation(u);
if(~ nd[u].suc[k ^ 1]) nd[nd[u].suc[k ^ 1]].pre = pre; nd[pre].suc[k] = nd[u].suc[k ^ 1];
nd[u].pre = prepre; if(! nd[pre].isRoot) nd[prepre].suc[getRelation(pre)] = u;
nd[pre].pre = u; nd[u].suc[k ^ 1] = pre;
if(nd[pre].isRoot) nd[pre].isRoot = 0, nd[u].isRoot = 1;
update(pre); update(u);
}
inline void splay(int u)
{
pushDown(u);
while(! nd[u].isRoot)
{
if(! nd[nd[u].pre].isRoot) rotate(getRelation(u) == getRelation(nd[u].pre) ? nd[u].pre : u);
rotate(u);
}
}
inline void access(int u)
{
splay(u);
if(~ nd[u].suc[1])
{
nd[u].productSum -= nd[nd[u].suc[1]].productSum;
nd[u].differenceSum -= nd[nd[u].suc[1]].differenceSum;
nd[nd[u].suc[1]].isRoot = 1; nd[u].suc[1] = -1;
}
while(~ nd[u].pre)
{
int pre = nd[u].pre; splay(pre);
if(~ nd[pre].suc[1])
{
nd[pre].productSum -= nd[nd[pre].suc[1]].productSum;
nd[pre].differenceSum -= nd[nd[pre].suc[1]].differenceSum;
nd[nd[pre].suc[1]].isRoot = 1; nd[pre].suc[1] = -1;
}
nd[pre].productSum += nd[u].productSum;
nd[pre].differenceSum += nd[u].differenceSum;
nd[pre].suc[1] = u; nd[u].isRoot = 0;
splay(u);
}
}
inline void link(int pre, int u)
{
access(pre); access(u);
ans += (double)(2 * nd[u].sz * nd[pre].productSum + sqr(nd[u].sz) * nd[pre].differenceSum) / sqr(n);
ans -= (double)nd[pre].a * sqr(nd[u].sz) / sqr(n);
nd[pre].tg += nd[u].sz; nd[pre].sz += nd[u].sz;
nd[pre].productSum += nd[u].sz * nd[pre].differenceSum;
nd[u].pre = pre;
nd[u].differenceSum -= nd[pre].a; nd[u].productSum -= (long long)nd[u].sz * nd[pre].a;
nd[u].lst = pre; nd[pre].bck.insert(u);
}
inline void cut(int u)
{
access(u);
ans += (double)(- 2 * nd[u].sz * nd[nd[u].suc[0]].productSum + sqr(nd[u].sz) * nd[nd[u].suc[0]].differenceSum) / sqr(n);
ans += (double)nd[nd[u].lst].a * sqr(nd[u].sz) / sqr(n);
nd[u].differenceSum = nd[u].a; nd[u].productSum = (long long)nd[u].a * nd[u].sz;
nd[nd[u].lst].bck.erase(nd[nd[u].lst].bck.find(u)); nd[u].lst = -1;
nd[nd[u].suc[0]].tg -= nd[u].sz; nd[nd[u].suc[0]].sz -= nd[u].sz;
nd[nd[u].suc[0]].productSum -= nd[u].sz * nd[nd[u].suc[0]].differenceSum;
nd[nd[u].suc[0]].pre = -1; nd[nd[u].suc[0]].isRoot = 1;
nd[u].suc[0] = -1;
}
inline void modify(int u, int x)
{
access(u);
int dlt = x - nd[u].a; nd[u].a = x;
ans += (double)dlt * sqr(nd[u].sz) / sqr(n);
nd[u].differenceSum += dlt; nd[u].productSum += (long long)nd[u].sz * dlt;
for(auto v : nd[u].bck)
{
splay(v); // 一定要先access, 否则没法保证之前的修改已经下传
ans -= (double)dlt * sqr(nd[v].sz) / sqr(n);
nd[v].differenceSum -= dlt; nd[v].productSum -= (long long)dlt * nd[v].sz;
}
}
inline int findRoot(int u) {while(! nd[u].isRoot) u = nd[u].pre; return u;}
}LCT;
int main()
{ #ifndef ONLINE_JUDGE freopen("worn.in", "r", stdin);
freopen("worn.out", "w", stdout); #endif using namespace Zeonfai;
n = getInt(); ans = 0;
for(int i = 2; i <= n; ++ i) LCT.link(getInt(), i);
for(int i = 1; i <= n; ++ i) LCT.modify(i, getInt());
printf("%.9lf\n", ans);
int m = getInt();
for(int i = 0; i < m; ++ i)
{
int opt = getInt(), x = getInt(), y = getInt();
if(opt == 2) LCT.modify(x, y);
else
{
LCT.access(y);
int u = LCT.findRoot(x);
if(u == y) LCT.cut(y), LCT.link(x, y);
else LCT.cut(x), LCT.link(y, x);
}
printf("%.9lf\n", ans);
}
}

2016集训测试赛(二十一)Problem C: 虫子的更多相关文章

  1. 2016集训测试赛(二十六)Problem A: bar

    Solution 首先审清题意, 这里要求的是子串而不是子序列... 我们考虑用1表示p, -1表示j. 用sum[i]表示字符串前\(i\)的前缀和. 则我们考虑一个字符串\([L, R]\)有什么 ...

  2. 2016集训测试赛(二十四)Problem B: Prz

    Solution 这道题有两个关键点: 如何找到以原串某一个位置为结尾的某个子序列的最晚出现位置 如何找到原串中某个位置之前的所有数字的最晚出现位置中的最大值 第一个关键点: 我们注意到每个数字在\( ...

  3. 2016集训测试赛(二十四)Problem C: 棋盘控制

    Solution 场上的想法(显然是错的)是这样的: 我们假设棋子是一个一个地放置的, 考虑在放置棋子的过程中可能出现哪些状态. 我们令有序整数对\((i, j)\)表示总共控制了\(i\)行\(j\ ...

  4. 2016集训测试赛(二十)Problem B: 字典树

    题目大意 你们自己感受一下原题的画风... 我怀疑出题人当年就是语文爆零的 下面复述一下出题人的意思: 操作1: 给你一个点集, 要你在trie上找到所有这样的点, 满足点集中存在某个点所表示的字符串 ...

  5. 2016集训测试赛(二十)Problem A: Y队列

    Solution 考虑给定一个\(n\), 如何求\(1\)到\(n\)的正整数中有多少在队列中. 不难注意到我们只需要处理质数次方的情况即可, 因为合数次方会被其因数处理到. 同时我们考虑到可能存在 ...

  6. 2016北京集训测试赛(十一)Problem C: 树链问题

    Solution 智障暴力题, 每个点维护一下子树信息, 树剖就好了. 我居然还傻了写了一发毛毛虫... #include <cstdio> #include <cctype> ...

  7. noip2017集训测试赛(十一)Problem C: 循环移位

    题面 Description 给定一个字符串 ss .现在问你有多少个本质不同的 ss 的子串 t=t1t2⋯tm(m>0)t=t1t2⋯tm(m>0) 使得将 tt 循环左移一位后变成的 ...

  8. 2016集训测试赛(十九)Problem C: 无聊的字符串

    Solution 傻X题 我的方法是建立后缀后缀树, 然后在DFS序列上直接二分即可. 关键在于如何得到后缀树上每个字符对应的字节点: 我们要在后缀自动机上记录每个点在后缀树上对应的字母. 考虑如何实 ...

  9. 2016集训测试赛(十九)Problem A: 24点大师

    Solution 这到题目有意思. 首先题目描述给我们提供了一种非常管用的模型. 按照题目的方法, 我们可以轻松用暴力解决20+的问题; 关键在于如何构造更大的情况: 我们发现 \[ [(n + n) ...

随机推荐

  1. Java语言基础---逻辑运算(长路短路运算)

    长路短路运算的区别 长路与运算&:是指在两边都是整数时,是逐位与运算,在两边是关系运算时,是逻辑运算. 短路与运算&&:是指从左至右,遇到false,则停止后面的运算. 长路或 ...

  2. day18 js 正则,UI框架,Django helloworld 以及完整工作流程

    JS正则:    text     判断字符串是否符合规定的正则表达式    exec    获取匹配的数据   默认情况下: 只要能匹配到就返回true 否则返回false 只匹配数字:   所以J ...

  3. 极简Node教程-七天从小白变大神(二:中间件是核心)

    当我们只引入express时,前述的那些功能都是没有启用的.那么,如何将这些功能添加进来呢?express通过其中间件机制实现了这些功能的管理.每一个中间件对应一个功能,而中间件可以是第三方库,也可以 ...

  4. 300万PV的ASP.NET网站使用阿里云的配置建议

    @老牛吃肉在博文“今天的访问高峰,扛过去了”的评论中询问了这样一个问题: 你好,站长,本公司正在考虑用阿里云.用途:互联网网站,主要站点:asp.net开发目前的考虑情况:访问ip 15-20万,pv ...

  5. 44、gridview实现下拉刷新、上拉加载更多(最简单实现上下拉操作的开源工程!)

    1.工程加入以下两个文件夹:(参考:https://github.com/jingchenUSTC/PullToRefreshAndLoad) (待会我会将demo打包上传) 2.这个demo只有一个 ...

  6. MongoDB快速入门学习笔记6 MongoDB的文档删除操作

    db.集合名称.remove({query}, justOne)query:过滤条件,可选justOne:是否只删除查询到的第一条数据,值为true或者1时,只删除一条数据,默认为false,可选. ...

  7. Webapp和后端交互检查测试

    除了功能,我们可以使用下面方法,查看交互过程,页面不能发现的问题: 什么是json 什么是json,json是什么,json如何使用 JSON是一种取代XML的数据结构,和xml相比,它更小巧但描述能 ...

  8. CSU-1908 The Big Escape

    CSU-1908 The Big Escape Description There is a tree-like prison. Expect the root node, each node has ...

  9. CSU-2172 买一送一

    CSU-2172 买一送一 Description ICPCCamp 有 n 个商店,用 1, 2, -, n 编号.对于任意 i > 1,有从商店 \(p_i\) 到 i 的单向道路. 同时, ...

  10. [c++面试准备]--vector对象是如何增长的

    参考资料:cpp primer 5th 背景: 为了支持快速的访问,vector/string将元素连续存储--每个元素都是紧挨着前一个元素存储. 如果我们向vector/string中添加新的元素, ...