【LOJ2587】铁人两项(APIO2018)-圆方树+树上统计

测试地址:铁人两项
做法:本题需要用到圆方树+树上统计。
首先有个结论:一个点双连通分量内的任意三个点,不管以什么顺序排列,都存在一条简单路径按顺序经过这三个点。
那么如果我们选定了路径的起点和终点,可以作为中间点的点,就是它们之间的路径所经过的所有点双连通分量中的点(除了两端)。于是我们就把圆方树建出来,方点的点权设为对应点双中的点数,为了去重,圆点的点权设为1,那么可以作为中间点的点数就等于两个点在圆方树路径上的点权和。于是我们很自然地转化成求每个点的贡献,也就是求经过该点的路径数量,这就是NOIP难度的树上统计了,那么我们就完成了这一题,时间复杂度为O(n)
我傻逼的地方:假圆方树毁一生……经过兜兜转转,总算找到了真正正确的圆方树写法,以前的做法都是假的……我真的是醉了…..
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,first[200010]={0},tot=0,firstt[200010]={0},totpbc;
int low[200010],dfn[200010],tim=0,st[200010],top=0;
bool vis[200010]={0};
ll ans=0,val[200010],sum=0;
struct edge
{
    int v,next;
}e[400010],t[200010];

void insert(int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
}

void insertt(int a,int b)
{
    t[++tot].v=b;
    t[tot].next=firstt[a];
    firstt[a]=tot;
}

void tarjan(int v,int fa)
{
    sum++;
    val[v]=0-1;
    low[v]=dfn[v]=++tim;
    st[++top]=v;
    vis[v]=1;
    int now=top;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa)
        {
            if (!vis[e[i].v])
            {
                tarjan(e[i].v,v);
                low[v]=min(low[v],low[e[i].v]);
                if (low[e[i].v]>=dfn[v])
                {
                    totpbc++;
                    val[totpbc]=1;
                    insertt(v,totpbc);
                    do
                    {
                        insertt(totpbc,st[top]);
                        val[totpbc]++;
                    }while(st[top--]!=e[i].v);
                }
            }
            else low[v]=min(low[v],dfn[e[i].v]);
        }
}

int solve(int v,bool type)
{
    ll siz=type;
    for(int i=firstt[v];i;i=t[i].next)
    {
        ll now=solve(t[i].v,!type);
        ans+=now*(sum-now)*val[v];
        siz+=now;
    }
    ans+=(sum-siz)*siz*val[v];
    if (type) ans+=(sum-1ll)*val[v];
    return siz;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b),insert(b,a);
    }

    tot=0,totpbc=n;
    for(int i=1;i<=n;i++)
        if (!vis[i])
        {
            sum=0;
            tarjan(i,0);
            solve(i,1);
        }
    printf("%lld",ans);

    return 0;
}
posted @ 2018-06-19 17:08  Maxwei_wzj  阅读(86)  评论(0编辑  收藏  举报