1. 程式人生 > 實用技巧 >【圓方樹+樹形dp】P4630 [APIO2018] Duathlon 鐵人兩項

【圓方樹+樹形dp】P4630 [APIO2018] Duathlon 鐵人兩項

題面:https://www.luogu.com.cn/problem/P4630

先用tarjan把原圖建成圓方樹

圓點賦值-1,方點賦值環的大小

對於每個點對,方案數為圓方樹上的路徑權值

然後利用樹形dp處理每個點對的貢獻

計運算元樹根節點經過的次數乘以2即可

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,m,head[maxn],size[maxn],cnt,tot,tott,v[maxn],h[maxn<<1],low[maxn],dfn[maxn],s[maxn],times,top;
struct edge { int to,nxt; }e[maxn<<1],G[maxn<<3]; void add(int x,int y) { e[++cnt].nxt=head[x]; e[cnt].to=y; head[x]=cnt; } void addd(int x,int y) { G[++tott].nxt=h[x]; G[tott].to=y; h[x]=tott; } void tarjan(int x) { low[x]=dfn[x]=++times; s[++top]=x; size[x]
=1; for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(!dfn[to]) { tarjan(to); low[x]=min(low[x],low[to]); if(low[to]==dfn[x]) { int xx=0,num=1; tot++; do { xx
=s[top--]; ++num; addd(tot,xx); addd(xx,tot); size[tot]+=size[xx]; }while(xx!=to); v[tot]=num; size[x]+=size[tot]; addd(x,tot); addd(tot,x); } } else low[x]=min(low[x],dfn[to]); } } long long ans=0; void dfs(int root,int u,int fa) { int tmp=(u<=n); ans+=2LL*size[u]*(size[root]-size[u])*v[u]; for(int i=h[u];i;i=G[i].nxt) { int to=G[i].to; if(to==fa) continue; ans+=2LL*tmp*size[to]*v[u]; tmp+=size[to]; dfs(root,to,u); } } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); scanf("%d%d",&n,&m); int x,y; tot=n; for(int i=1;i<=n;i++) v[i]=-1; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } for(int i=1;i<=n;i++) if(!dfn[i]) { tarjan(i); dfs(i,i,0); } printf("%lld",ans); return 0; }