莱芜网站设计公司制作图片文字的软件
题意
比特镇的路网由 mmm 条双向道路连接的 nnn 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:
- 先选择三个两两互不相同的路口 sss、ccc 和 fff,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
- 选择一条从 sss 出发,经过 ccc 最终到达 fff 的路径。考虑到安全因素,选择的路径经过同一个点至多一次。
在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 sss、ccc 和 fff 的方案,使得在第 222 步中至少能设计出一条满足要求的路径。
n≤100000n \leq 100000n≤100000,m≤200000m \leq 200000m≤200000。
思路
在这里我初步见识到了圆方树的优美性质以及应用了。这一题则是圆方树上巧赋点权的经典题目。
那么在一个 vdcc 中,我们要从 uuu 经过 xxx 走到 vvv,必然存在一个不走重复点的路径。即在方点对应的 vdcc 上,除了用来连接其它方点的割点,其它点都是可行的途经点。
然后这就变成了一个树形 dp 了。典型的,像这题一样,考虑计算每个点 uuu 作为途经点的方案数(方点作为途径就代表 vdcc 内所有点)。每个圆点可以成为出发点,于是由维护子树大小改为子树内维护圆点个数 circucirc_ucircu。
对于未合并的 u,vu,vu,v 由边 (u,v)(u,v)(u,v) 相连。根据乘法原理,有 uuu 子树点与儿子 vvv 子树点配对:circu×circvcirc_u\times circ_vcircu×circv。uuu 合并所有儿子后,和子树外其它点配对:circu×(ltk−circu)circ_u\times (ltk-circ_u)circu×(ltk−circu),ltkltkltk 为连通块内圆点个数。
上文说,vdcc 中除了割点的所有点都可以选为途经点,那我们怎么去重呢?在此给每一个点赋予点权:
- 方点的点权是对应 vdcc 大小;
- 圆点的点权是 −1-1−1。
这个方法真是太天才了。等到走到方点时,就已经或者即将可以减去了圆割点作为途径点的方案数,圆割点作为起点的方案数依然保留;而且圆点作为叶节点时因为没有儿子,circv=0circ_v=0circv=0,也不会产生其它奇怪的贡献。
因为起点终点可以调转,记得方案数 ×2\times 2×2。
具体细节见代码:
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9,M=2e5+9;
ll n,m;
struct edge
{ll to,next;
}e[M<<1];
ll idx,head[N];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll dfn[N],low[N],ti;
ll stk[N],top;
bool iscut[N];
ll vdccnt,vdcno[N];
vector<ll>vdcc[N];
ll val[M];
ll ltk;
void tarjan(ll u,ll fa)
{ltk++;dfn[u]=low[u]=++ti;stk[top++]=u;ll son=0;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(!dfn[v]){son++;tarjan(v,u);low[u]=min(low[u],low[v]);if(low[v]>=dfn[u]){vdccnt++;while(1){ll tem=stk[--top];vdcc[vdccnt].push_back(tem);vdcno[tem]=vdccnt;if(v==tem)break;}vdcc[vdccnt].push_back(u);vdcno[u]=vdccnt;val[vdccnt+n]=vdcc[vdccnt].size();}}if(v!=fa)low[u]=min(low[u],dfn[v]);}if(son==0&&fa==0)vdcc[++vdccnt].push_back(u);if(son==2&&fa==0)iscut[u]=1;
}
vector<ll>G[M];
ll las;
ll ans,circ[M];
void dfs(ll u,ll fa)
{circ[u]=(u<=n);for(auto v:G[u]){if(v==fa)continue;dfs(v,u);ans=ans+val[u]*circ[u]*circ[v];circ[u]+=circ[v];}ans=ans+val[u]*circ[u]*(ltk-circ[u]);
}
void sol(ll u)
{for(int i=las+1;i<=vdccnt;i++){for(auto u:vdcc[i]){G[u].push_back(i+n);G[i+n].push_back(u);}}dfs(u,0);las=vdccnt;
}
int main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++)val[i]=-1;for(int i=1;i<=m;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}for(int i=1;i<=n;i++){if(!dfn[i]){ltk=0;tarjan(i,0);sol(i);}}printf("%lld",ans*2);return 0;
}
