[APIO2018]铁人两项(圆方树)

过了14个月再重新看这题,发现圆方树从来就没有写过。然后写了这题发现自己APIO2018打铁的原因竟然是没开long long,将树的部分的O(n)写挂了(爆int),毕竟去年APIO时我啥都不会,连tarjan都写不来,活该打铁。

不扯了写题解。

首先建立圆方树,然后任意枚举圆点s和f,然后c可以在这两个点路径中每个点双的点挑选。所以令圆点值为-1,方点值为点双大小,然后选法是圆点路径权值和。然后计算每个点出现多少次,可以对每个连通块树形DP求解,然后这道题就没了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+7;
vector<int>G1[N],G2[N];
int n,m,tot,sum,tim,top,low[N],dfn[N],q[N],sz[N],val[N];
ll ans;
void tarjan(int u)
{
    dfn[u]=low[u]=++tim,q[++top]=u;
    sz[u]=1,val[u]=-1;
    for(int i=0;i<G1[u].size();i++)
    if(!dfn[G1[u][i]])
    {
        int v=G1[u][i];
        tarjan(v),low[u]=min(low[u],low[v]);
        if(low[v]>=dfn[u])
        {
            G2[u].push_back(++tot),val[tot]=1;
            int x=0;
            do{
                x=q[top--],G2[tot].push_back(x);
                sz[tot]+=sz[x],val[tot]++;
            }while(x!=v);
            sz[u]+=sz[tot];
        }
    }
    else low[u]=min(low[u],dfn[G1[u][i]]);
}
void dfs(int u)
{
    if(u<=n)ans+=1ll*(sum-1)*val[u];
    ans+=1ll*(sum-sz[u])*sz[u]*val[u];
    for(int i=0;i<G2[u].size();i++)
    ans+=1ll*(sum-sz[G2[u][i]])*sz[G2[u][i]]*val[u],dfs(G2[u][i]);
}
int main()
{
    scanf("%d%d",&n,&m),tot=n;
    for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),G1[x].push_back(y),G1[y].push_back(x);
    for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i),sum=sz[i],dfs(i);
    printf("%lld",ans);
}
View Code
原文地址:https://www.cnblogs.com/hfctf0210/p/11154790.html