题解 P4020 【[CTSC2012]电阻网络】

SuperJvRuo

2018-10-28 12:05:46

Solution

本篇题解符号约定: | 物理量 | 符号 | | ----------- | ----------- | | 电流 | I | | 电动势 | E | | 电阻 | R | | 电势差(电压) | U | | 电势 | $\varphi$ | 这是一道电学题,我们复习一些电学内容: ### 1.欧姆定律 在同一电路中,通过某段导体的电流跟这段导体两端的电压成正比,跟这段导体的电阻成反比,即: $$I=\frac{U}{R}$$ ### 2.基尔霍夫第一定律 汇合于任一节点处的各电流的代数和等于0,即: $$\sum I=\sum I_{in}+\sum I_{out}=0$$ 对于一些电学题目,可以依据欧姆定律和基尔霍夫第一定律列出方程组,利用高斯消元求解。 但是这题数据范围50000,高斯消元只能获得30pts。我们注意到树上的最长链长度不超过50,应考虑由父亲到儿子或是儿子到父亲的递推关系,暴力递推答案。 显然,只要本题中所有的电阻器阻值相同,不论每个电阻的阻值是$10000\Omega$还是$0.1\Omega$,答案都是一样的。因此我将每个电阻的阻值记为$R$。为了便于计算,钦点一个叶子节点为根。 考虑节点$i$: 设$i$的父节点为$fa$,子节点集合为$son$,$fa$到$i$的电流为$I_i$。 由父节点$fa$到$i$的电路中,电阻处电势的下降,加上电源带来的电势上升,其结果就等于两点的电势差: $$I_iR+E_i=\varphi_{fa}-\varphi_{i}$$ 移项得: $$I_i=\frac{\varphi_{fa}-\varphi_{i}-E_i}{R}$$ 同理,由$i$到每个子节点$x$的电路中,都有: $$I_xR+E_x=\varphi_{i}-\varphi_{x}$$ 移项,对所有子节点求和得: $$\sum_{x\in son}I_x=\sum_{x\in son}\frac{\varphi_{i}-\varphi_{x}-E_x}{R}$$ $i$点的基尔霍夫第一定律: $$\sum_{x\in son}I_x=I_i$$ 将已求得的两个$I$带入第三个式子: $$\sum_{x\in son}\frac{\varphi_{i}-\varphi_{x}-E_x}{R}=\frac{\varphi_{fa}-\varphi_{i}-E_i}{R}$$ 整理得: $$\varphi_i=\frac{\varphi_{fa}-E_i+\sum_{x\in son}(\varphi_x+E_x)}{deg_i}$$ 为了暴力递推,设$\varphi_i=K_i\varphi_{fa}+B_i$,其中$K_i$和$B_i$都是只与$i$与$son$有关的系数,将上式中的$\varphi_x$以这种形式表示,即得: $$\varphi_i=\frac{\varphi_{fa}-E_i+\sum_{x\in son}(K_x\varphi_i+B_x+E_x)}{deg_i}$$ 提出系数: $$K_i=\frac{1}{deg_i-\sum_{x\in son}K_x}$$ $$B_i=\frac{\sum_{x\in son}(B_x+E_x)-E_i}{deg_i-\sum_{x\in son}K_x}=K_i(\sum_{x\in son}(B_x+E_x)-E_i)$$ 豁然开朗了是不是? 诶不对啊,我们要求的是$i$到地面的电势差啊?我们还要思考一下接地节点的边界处理。 叶子节点$i$在原图中的度数为1,算上接地的一条边,度数为2。地面的电势为$0$。因此: $$\varphi_i=\frac{\varphi_{fa}-E_i}{2}$$ 故: $$K_i=\frac{1}{2}$$ $$B_i=\frac{-E_i}{2}$$ 这样,我们求得的$\varphi_i$就是$i$点到地面的电势差了。 $K$与电路中的电源无关,可以预处理直接求。加电源的时候暴力向上修改$B$即可。查询电势差的时候也是向上dark♂力扫一遍。由于链长不超过50,其复杂度完全正确。 ``` #include<cstdio> #include<cctype> #include<algorithm> struct Edge { int to,next; }edge[100005]; int head[50005],cnt; void Add_edge(int u,int v) { edge[++cnt]=(Edge){v,head[u]}; head[u]=cnt; edge[++cnt]=(Edge){u,head[v]}; head[v]=cnt; } int root; int degree[50005],fa[50005]; double k[50005],b[50005],sum_b[50005],U[50005],sum_u[50005]; void init_k(int u,int f) { fa[u]=f; double sum_k=0; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v!=f) { init_k(v,u); sum_k+=k[v]; } } k[u]=1.0/(degree[u]-sum_k); } void Add_b(int u,int e) { if(u==0) return; else { sum_b[fa[u]]-=b[u]; b[u]=(sum_b[u]+sum_u[u]-U[u])*k[u]; sum_b[fa[u]]+=b[u]; Add_b(fa[u],e); } } void Modify(int u,int v,int e) { if(fa[v]==u) { std::swap(u,v); e=-e; } U[u]+=e; sum_u[v]+=e; Add_b(u,e); } double Query(int u) { if(u==0) { return 0; } return k[u]*Query(fa[u])+b[u]; } int main() { int n,m,u,v,e; scanf("%d%d",&n,&m); for(int i=1;i<n;++i) { scanf("%d%d",&u,&v); Add_edge(u,v); ++degree[u]; ++degree[v]; } for(int i=1;i<=n;++i) { if(degree[i]==1) { Add_edge(i,0); degree[0]=1; break; } } for(int i=1;i<=n;++i) { if(degree[i]==1) degree[i]=2; } init_k(0,0); while(m--) { char ch=getchar(); while(!isalpha(ch)) { ch=getchar(); } if(ch=='Q') { scanf("%d",&u); printf("%.12lf\n",Query(u)); } else { scanf("%d%d%d",&u,&v,&e); Modify(u,v,e); } } return 0; } ```