LOJ #2587. 「APIO2018」铁人两项 (圆方树,树形DP)
开始的时候没有借助圆方树去思考,思路非常混乱,想了很长时间后冷静下来发现这题不就是分类讨论简单题嘛...
题目不难,分两种情况讨论:
设当前点为中间点(圆点)
- 起点从一个儿子的子树进入到中间点后进入到另一个儿子的子树.
- 起点从一个儿子的子树进入到中间点后仍然回到该儿子子树中(相当于上一条的子问题)
- 然后对于儿子(方点)直接累加,父亲(方点)要扣掉当前子树的影响.
code:
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #define N 200006 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; vector<int>G[N]; ll ans; int edges,tim,tot,n,m,fr,SIZE; int hd[N<<1],to[N<<1],nex[N<<1],dfn[N],low[N],S[N],deg[N],size[N]; ll g[N],F[N]; void add(int u,int v) { nex[++edges]=hd[u],hd[u]=edges,to[edges]=v; } void tarjan(int x) { dfn[x]=low[x]=++tim; S[++fr]=x; ++SIZE; for(int i=hd[x];i;i=nex[i]) { int y=to[i]; if(!dfn[y]) { tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]) { ++tot; G[x].push_back(tot); G[tot].push_back(x); ++deg[tot]; for(int p=0;p!=y;--fr) { p=S[fr]; ++deg[tot]; G[tot].push_back(p); G[p].push_back(tot); } } } else { low[x]=min(low[x],dfn[y]); } } } void dfs(int x,int ff) { size[x]=(x<=n); for(int i=0;i<G[x].size();++i) { int y=G[x][i]; if(y==ff) continue; dfs(y,x); size[x]+=size[y]; } } void solve(int x,int ff) { if(x<=n) { int cu=0; for(int i=0;i<G[x].size();++i) { int y=G[x][i]; if(y==ff) cu=SIZE-size[x]; else cu=size[y]; ans+=1ll*cu*(SIZE-cu-1); } if(ff) { ans+=g[ff]-1ll*size[x]*SIZE; ans-=1ll*size[x]*(SIZE-2*size[x]); } } else { for(int i=0;i<G[x].size();++i) { int y=G[x][i]; if(y==ff) continue; F[x]+=1ll*size[y]*(size[x]-size[y]); g[x]+=1ll*size[y]*(SIZE-size[y]); } g[x]+=1ll*size[x]*(SIZE-size[x]); ans+=1ll*F[x]; } for(int i=0;i<G[x].size();++i) if(G[x][i]!=ff) solve(G[x][i],x); } int main() { // setIO("input"); scanf("%d%d",&n,&m); tot=n; for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); add(x,y),add(y,x); } for(int i=1;i<=n;++i) { if(!dfn[i]) { tim=SIZE=fr=0; tarjan(i); dfs(i,0); solve(i,0); } } printf("%lld\n",ans); return 0; }