【APIO2018】鐵人兩項(圓方樹,動態規劃)
阿新 • • 發佈:2019-01-03
【APIO2018】鐵人兩項(圓方樹,動態規劃)
題面
題解
嚶嚶嚶,APIO的時候把一個組合數寫成階乘了,然後這題的70多分沒拿到
首先一棵樹是很容易做的,隨意指定起點終點就只能在兩點路徑上選擇第三點。那麼考慮過中點的路徑個數,就可以很方便的\(dp\)計算了。
對於仙人掌而言,把環全部縮成點,轉成樹,縮起來的點額外定義一個點權,同樣可以直接在樹上做\(dp\),額外考慮環自身內部的貢獻。
那麼對於一般圖而言,構建圓方樹,那麼選定起點和終點後,還是隻能選擇兩點路徑之間的圓點。定義方點的權值為點雙的點數和,顯然選定起點終點後,兩點間的貢獻就是路徑上的點權和。然而這樣子圓點會被算多,所以圓點的點權為\(-1\)
那麼又是一棵樹了,直接列舉中間點考慮過中間點的路徑個數即可。
#include<iostream> #include<cstdio> using namespace std; #define ll long long #define MAX 100100 inline int read() { int x=0;bool t=false;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } struct Graph { struct Line{int v,next;}e[MAX<<3]; int h[MAX<<1],cnt=1; inline void Add(int u,int v) { e[cnt]=(Line){v,h[u]};h[u]=cnt++; e[cnt]=(Line){u,h[v]};h[v]=cnt++; } }Gr,Tr; int n,m;ll ans; int dfn[MAX],low[MAX],tim,S[MAX],top,tot,V[MAX<<1]; int size[MAX<<1],Size; void Tarjan(int u) { dfn[u]=low[u]=++tim;S[++top]=u;++Size; for(int i=Gr.h[u];i;i=Gr.e[i].next) { int v=Gr.e[i].v; if(!dfn[v]) { Tarjan(v);low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]) { Tr.Add(++tot,u);int x;V[tot]=1; do{x=S[top--];Tr.Add(tot,x);++V[tot];}while(x!=v); } } else low[u]=min(low[u],dfn[v]); } } void dp(int u,int ff) { size[u]=u<=n; for(int i=Tr.h[u];i;i=Tr.e[i].next) { int v=Tr.e[i].v;if(v==ff)continue; dp(Tr.e[i].v,u); ans+=2ll*V[u]*size[u]*size[v]; size[u]+=size[v]; } ans+=2ll*V[u]*size[u]*(Size-size[u]); } int main() { n=tot=read();m=read(); for(int i=1;i<=m;++i)Gr.Add(read(),read()); for(int i=1;i<=n;++i)V[i]=-1; for(int i=1;i<=n;++i)if(!dfn[i])Size=0,Tarjan(i),dp(i,0); printf("%lld\n",ans); return 0; }