[BZOJ3091]城市旅行
壹、题目描述
贰、一些思考
目测 \(\tt LCT\) .
对于一条路径,设点排下来是 \(p_1,p_2,p_3,...,p_k\),那么这条路径的 \(E\) 的值即为
为了简便表达,我们将 \(v_{p_i}\) 直接写成 \(p_i\).
对于一个点,我们肯定得维护答案,考虑当前根为 \(t\),那么 \(t\) 中一定需要维护上面那个值的分子,我们记这个东西为 \(\rm exp\),但是在由子到父亲上传的时候,对于 \(t\) 的左子树,在它的根中,维护的比我们想要的要少一些,少的部分其实就是左子树的点和右子树匹配的情况,对于左子树我们单独再储存一个 \(1\times p_1+2\times p_2+...+(t-1)\times p_{t-1}\),记这个变量为 \(\rm lsum\),那么少的部分就是 \(\text{lsum}\times (\text{siz}_r+1)\),这是左边的点和右边匹配的情况,同样,我们便也需要存下一个 \(\text{rsum}=1\times p_k+2\times p_{k-1}+....\) 为右子树的情况,上传同理;
考虑我们接下来需要维护的变量
- \(\rm siz\),不必多说;
- \(\rm val\),就是这个点的点值;
- \(\exp\),起到类似于储存答案的功效;
- \(\rm lsum,rsum\),上传时有奇效;
- \(\rm sum\),处理 \(\rm lsum,rsum\) 时需要使用,表示子树内所有点的 \(\rm val\) 之和;
但是还有一个操作 —— 路径权值修改。对于 \(\rm lsum,rsum\) 的修改都比较简单,有 \(\Delta=x\times (1+2+3+...+k)=x{k(k+1)\over 2}\). 修改的难点主要在于 \(\rm exp\) 的修改上,对于它,我们不妨将式子写出来:
最后一步的推导直接展开合并写成通项即可。
所以对于 \(\exp\) 的修改也可以直接做了,只要我们有 \(k\) 即 \(\rm siz\) .
然后,就切掉了?剩下的,就是如何用代码实现这个过程了。
时间复杂度 \(\mathcal O(n\log n)\).
叁、参考代码
\(\color{red}{\text{talk is LCT, show you the code.}}\)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define mp(a, b) make_pair(a, b)
typedef long long ll;
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
const int maxn=5e4;
ll gcd(ll x, ll y){
return !y? x: gcd(y, x%y);
}
int n, m;
namespace lct{
int son[maxn+5][2], fa[maxn+5];
int siz[maxn+5], rev[maxn+5];
ll tag[maxn+5], lsum[maxn+5], rsum[maxn+5], exp[maxn+5], sum[maxn+5], val[maxn+5];
inline int isroot(int x){
return son[fa[x]][0]!=x && son[fa[x]][1]!=x;
}
inline void reverse(int x){
if(!x) return;
swap(son[x][0], son[x][1]);
swap(lsum[x], rsum[x]);
rev[x]^=1;
}
inline void add(int x, ll delta){
if(!x) return;
tag[x]+=delta, val[x]+=delta;
lsum[x]+=delta*siz[x]*(siz[x]+1)/2;
rsum[x]+=delta*siz[x]*(siz[x]+1)/2;
sum[x]+=delta*siz[x];
exp[x]+=delta*siz[x]*(siz[x]+1)*(siz[x]+2)/6;
}
inline void pushup(int x){
int ls=son[x][0], rs=son[x][1];
siz[x]=siz[ls]+siz[rs]+1;
sum[x]=sum[ls]+sum[rs]+val[x];
lsum[x]=lsum[ls]+(siz[ls]+1)*(val[x]+sum[rs])+lsum[rs];
rsum[x]=rsum[rs]+(siz[rs]+1)*(val[x]+sum[ls])+rsum[ls];
exp[x]=exp[ls]+exp[rs]+lsum[ls]*(siz[rs]+1)+rsum[rs]*(siz[ls]+1)+val[x]*(siz[ls]+1)*(siz[rs]+1);
}
inline void pushdown(int x){
if(tag[x]) add(son[x][0], tag[x]), add(son[x][1], tag[x]);
if(rev[x]) reverse(son[x][0]), reverse(son[x][1]);
tag[x]=rev[x]=0;
}
inline void connect(int x, int y, int d){
son[x][d]=y, fa[y]=x;
}
inline void rotate(int x){
int y=fa[x], z=fa[y], d=(son[y][1]==x);
fa[x]=z; if(!isroot(y)) son[z][son[z][1]==y]=x;
connect(y, son[x][d^1], d);
connect(x, y, d^1);
pushup(y), pushup(x);
}
inline void splay(int x){
static int sta[maxn+5], ed=0; int now=x;
while(sta[++ed]=now, !isroot(now)) now=fa[now];
while(ed) pushdown(sta[ed--]);
int y, z;
while(!isroot(x)){
y=fa[x], z=fa[y];
if(!isroot(y))
(son[z][1]==y)^(son[y][1]==x)? rotate(y): rotate(x);
rotate(x);
}
}
inline void access(int x){
for(int pre=0; x; pre=x, x=fa[x])
splay(x), son[x][1]=pre, pushup(x);
}
inline int findrt(int x){
access(x); splay(x); pushdown(x);
while(son[x][0]) pushdown(x=son[x][0]);
return splay(x), x;
}
inline void makert(int x){
access(x); splay(x); reverse(x);
}
inline void link(int x, int y){
makert(x);
if(findrt(y)==x) return;
fa[x]=y;
}
inline void cut(int x, int y){
makert(x);
if(findrt(y)==x && fa[y]==x && son[y][0]==0)
son[x][1]=fa[y]=0, pushup(x);
}
inline void alladd(int x, int y, ll delta){
makert(x);
if(findrt(y)!=x) return;
access(y); splay(y);
add(y, delta);
}
inline pair<ll, int> query(int x, int y){
makert(x);
if(findrt(y)!=x) return mp(-1, -1);
access(y); splay(y);
return mp(exp[y], siz[y]);
}
inline void print(){
for(int i=1; i<=n; ++i){
printf("node %d :>\n", i);
printf("fa == %d\n", fa[i]);
printf("ls == %d, rs == %d\n", son[i][0], son[i][1]);
printf("val == %lld, siz == %d\n", val[i], siz[i]);
printf("sum == %lld, exp == %lld\n", sum[i], exp[i]);
puts("------------------------------");
}
}
inline void initial(){
for(int i=1; i<=n; ++i){
siz[i]=1, lsum[i]=rsum[i]=exp[i]=sum[i]=val[i]=readin(1);
}
int u, v;
for(int i=1; i<n; ++i){
u=readin(1), v=readin(1);
link(u, v);
}
}
}
inline void input(){
n=readin(1), m=readin(1);
lct::initial();
}
signed main(){
input();
int cmd, x, y, d;
while(m--){
cmd=readin(1), x=readin(1), y=readin(1);
if(cmd==1) lct::cut(x, y);
else if(cmd==2) lct::link(x, y);
else if(cmd==3){
d=readin(1);
lct::alladd(x, y, d);
}
else if(cmd==4){
pair<ll, int>ret=lct::query(x, y);
ll up=ret.first, down=ret.second;
if(up==-1){ printf("-1\n"); continue; }
down=down*(down+1)/2;
ll d=gcd(up, down);
printf("%lld/%lld\n", up/d, down/d);
}
}
return 0;
}
注意执行 reverse
的时候也要把 \(\rm lsum,rsum\) 进行交换。
肆、用到の小 \(\tt trick\)
本题计算期望的方法是 \(\cfrac{\text{整体}}{\text{整体}}\),考虑每个点被算进多少次路径,得到这个点对期望的总价值贡献,求和,以方案数除之,是答案也。
另外,对于这种题,似乎有时可以从询问方面得到我们需要维护什么,对于修改时,再考虑怎么维护这些值即可。
但是可能也会出现我们一开始无法直接维护答案,因为不是所有的答案都可以直接维护,所以有时可以将答案拆成多个部分,分开进行维护,然后拼接起来,比如此题并未将 \(k\choose 2\) 放入维护中,为之高次,亦于分母耶,是大繁也。