1. 程式人生 > >【APIO2018】鐵人兩項(圓方樹,動態規劃)

【APIO2018】鐵人兩項(圓方樹,動態規劃)

【APIO2018】鐵人兩項(圓方樹,動態規劃)

題面

UOJ
洛谷
BZOJ

題解

嚶嚶嚶,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;
}