铁人两项

  题目描述:

  给定无向图G,包含n个点m条边(不保证连通),求有序三元组(s,c,f)个数要求满足 s, c, f 都是图中的点,且存在一条从s到c的路径和一条从c到f的路径,使得两条路径没有公共点(除c外)。

  在这里我们引进圆方树的概念。 

  但首先我们要了解一下点双和边双。

  在无向图中:

    点双:极大的连通子图,使得删掉这个子图中的任何一个点,这个子图仍然连通。

  也就是说,在这个极大的连通子图中没有割点。

  也就是说,在这个极大的连通子图中两个不同节点可以通过两条没有公共点的路径互相到达,但是排除起点和终点。

  所以,u——v这也是个点双。      

    边双:极大的连通子图,使得删掉这个子图中的任何一条边,这个子图仍然连通。

  也就是说,在这个极大的连通子图中没有割边。

  也就是说,在这个极大的连通子图中两个不同节点可以通过两条没有公共边的路径互相到达。

  

  那么我们考虑如何求点双,边双:

  边双:很简单,我们断开所有的割边,剩下的每个连通子图就是一个边双强连通分量。

  代码:

void tarjan(int u, int pre)
{
    stk[++top] = u;
    dfn[u] = low[u] = ++idx;
    for(int e = adj[u], v; e; e = nxt[e])
    {
        if(!dfn[v = go[e]])
        {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u])
            {
                is_bridge[e] = is_bridge[e^1] = 1;
                cnt++;
                do
                    bel[stk[top]] = cnt;
                while(stk[top--] != v);
            }
        }
        else if(v != pre)
            low[u] = min(low[u], dfn[v]);
    }
}     

  点双:那么是否删去所有割点,剩 下的连通块都是点双呢?

  …不是的!一个点可能属于不同的点双!

  比如 a——b——c ,b属于两个点双集合。

  求点双有两种方法:

    一种求点双的?法是把边压入栈中,当发现low[v] >= dfn[u] 时,把边(u,v)及它以上的所有边从栈中弹出,这些边所涉及的点共同形成?个点双(显然包括u)。

    另?种方法是像别的Tarjan算法?样把点压入栈中,当发现 low[v] >= dfn[u]时,把v及v以上的点从栈中弹出,再加上u,共同形成?个点双。

  我个人喜欢后一种……

  有几个细节需要注意:

    1.弹元素的时候,我们不能弹u,我们弹到v,因为到u的所有元素可能构成别的点双。

      比如这样一个图:

      a - u , u - b , b - a , u - v , v - x , x - u 。

      这样的图,栈是这样的:(从上往下):x,v,b,u,a.

      那么我们从v回溯到u的时候,第一次弹元素,u应该和v,x构成一个点双。但是v和u之间还有一个b,我们显然不能将b放到这个点双集合中(就因为v和u之间可能还有其他元素),所以我们只能弹到v。

    2.虽然弹到v,u也要算在这个点双集合中。

      因为既然到v才满足>=关系,说明下面一定有一个点连到u这里,u也一定包含在这个点双集合中,不能忘了!

  代码:

void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++tot;
    st[++top]=u;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])
            {
                num++;
                do
                {
                    ...
                    top--;
                }
                while(st[top]!=v)
            }
        }
        else if(v!=fa)
            low[u]=min(low[u],dfn[v]);
    }
} 

  先在我们可以引进圆方树的概念了:

  

  那么圆方树怎么建呢?

  其实很容易,只要对于每一个割点u的孩子v,元素弹到v(包含v),他们和u共同构成一个点双强连通分量(上文已经解释过),那么我们把这些点和u连到一个方点上就可以了。

  知道了点双边双,会建圆方树,我们来看看这道题:

  我们首先考虑 O(n2) 的做法,就是枚举两个端点,这两个端点在我们的圆方树上之间有且只有一条路径。那么这条路径上既园点又有方点。所有方点所对应的原点都可以做为我们的中转点,于是每一个方点的权值我们都记录这个点所连的圆点个数,我们希望把这些方点的权值加起来求路径长。但是我们发现有重复,相邻的方点都包含他们之间的圆点,所以我们把圆点的权值设为-1,这样就保证每个圆点只被统计一遍。

  其实我们的问题已经转化成:统计树上所有路径上的点权权值和之和了,其中圆点可以作为起点和终点,方点不可以。

  枚举路径显然不优,所以我们考虑计算每个点的贡献。

  我们设sum为这个圆方树上圆点的个数(注意是圆点的个数!因为方点没有实际意义,圆点才是真正可以选的点,方点只是方便计算答案而设计的)

  对于一个方点,它只能作为中转点,那么经过这个点的路径有(sum-sz[u])*sz[u]+sz[v]*(sum-sz[v]);(v是u的儿子)不减1是因为方点不是一个真正的点。

  对于一个圆点,作为中转点时,经过这个点的路径有:(sum-sz[u])*(sz[u]-1)+sz[v]*(sum-sz[v]-1);v是u的儿子)不减1是因为圆点是一个真正的点。

  这样复杂度就是O(n)了!

  代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 400000
#define ll long long
int head[maxn],to[maxn],nxt[maxn],w[maxn],st[maxn];
int H[maxn],T[maxn],N[maxn],dfn[maxn],low[maxn],sz[maxn];
int cnt,CNT,n,m,num,tot,top,sum;
ll ans;
void add(int a,int b)
{
    to[++cnt]=b;
    nxt[cnt]=head[a];
    head[a]=cnt;
}
void ADD(int a,int b)
{
    T[++CNT]=b;
    N[CNT]=H[a];
    H[a]=CNT;
}
void Tarjan(int u,int fa)
{
    dfn[u]=low[u]=++tot;
    st[++top]=u;
    for(int i=head[u]; i; i=nxt[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            Tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])
            {
                int am=0;
                ++num;
                while(st[top]!=v)
                {
                    am++;
                    ADD(st[top],n+num);
                    ADD(n+num,st[top]);
                    top--;
                }
                am++;
                ADD(v,n+num);
                ADD(n+num,v);
                top--;
                am++;
                ADD(u,n+num);
                ADD(n+num,u);
                w[n+num]=am;
            }
        }
        else if(v!=fa) low[u]=min(low[u],dfn[v]);
    }
}
void dfs(int u,int fa)
{
    if(w[u]==-1) sz[u]=1;
    for(int i=H[u]; i; i=N[i])
    {
        int v=T[i];
        if(v==fa) continue;
        dfs(v,u);
        sz[u]+=sz[v];
    }
}
void dp(int u,int fa)
{
    if(w[u]==-1)
        ans+=1ll*(sum-1)*w[u]*2;
    ans+=1ll*(sum-sz[u])*w[u]*(sz[u]-(w[u]==-1));
    for(int i=H[u]; i; i=N[i])
    {
        int v=T[i];
        if(v==fa) continue;
        ans+=1ll*sz[v]*(sum-(w[u]==-1)-sz[v])*w[u];
        dp(v,u);
    }
}
void solve(int u)
{
    dfs(u,0);
    sum=sz[u];
    dp(u,0);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++) w[i]=-1;
    for(int i=1; i<=m; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    for(int i=1; i<=n; i++)
    {
        if(!dfn[i])
        {
            Tarjan(i,0);
            solve(i);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

  以及——别忘开long long!加油呀!

原文地址:https://www.cnblogs.com/popo-black-cat/p/11219120.html

时间: 2024-11-10 16:13:06

铁人两项的相关文章

【APIO2018】铁人两项

[APIO2018]铁人两项 题目描述 大意就是给定一张无向图,询问三元组\((s,c,f)\)中满足\(s\neq c\neq f\)且存在\((s\to c\to f)\)的简单路径(每个点最多经过一次)的数量. \(1\leq n,\leq 10^5,1\leq m\leq 2*10^5\) 我们考虑枚举\(s,f\)然后计算中间\(c\)的数量.我们发现对于一张图上统计两点之间路径上的点数量很好做.于是我们考虑建圆方树. 我们将圆点的权值定为\(-1\),将方点的权值定为与其直接相连的圆

[圆方树] Luogu P4630 Duathlon 铁人两项

题目描述 比特镇的路网由 mm 条双向道路连接的 nn 个交叉路口组成. 最近,比特镇获得了一场铁人两项锦标赛的主办权.这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程. 比赛的路线要按照如下方法规划: 先选择三个两两互不相同的路口 s, cs,c和 ff,分别作为比赛的起点.切换点(运动员在长跑到达这个点后,骑自行车前往终点).终点. 选择一条从 ss出发,经过 cc最终到达 ff的路径.考虑到安全因素,选择的路径经过同一个点至多一次. 在规划路径之前,镇长想请你帮忙计

luogu 4630 [APIO2018] Duathlon 铁人两项

题目大意: 无向图上找三个点 a b c使存在一条从a到b经过c的路径 求取这三个点的方案数 思路: 建立圆方树 这个圆方树保证没有两个圆点相连或两个方点相连 对于每个节点x 设该节点为路径的中间节点 则a c要么同在一个子树内 要么一个在子树内另一个在子树外 最后对答案<<1 对于每个方点设val[x] 为该点所连圆点的个数 每个方点按照两种情况算出答案之后 用圆点减去算重的部分 1 #include<iostream> 2 #include<cstdio> 3 #i

【BZOJ】【2765】【JLOI2010】铁人双项比赛

计算几何/半平面交 本来我是想去写POJ 1755的,然后想起了这道跟它很像的题,但应该是弱化版,所以就先写了这个…… 我们可以发现每个人的总用时,与k是呈一次函数关系的:$time_i=\frac{k}{Vrun_i}+\frac{S-k}{Vride_i}$ 然而我们要找的是某个k,使得$min(time_n-time_i)$最大 那么就是一个线性规划问题了……这个也可以用半平面交来做……(蒟蒻并不会单纯形) 下面的部分为了偷懒简洁我就用$a_i$和$b_i$来代替两种速度…… 我一开始想的

为什么Fibonacci数列相邻两项之比会趋于0.618

转帖: http://www.matrix67.com/blog/archives/5221 你或许熟知一个非常经典的结论: Fibonacci 数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, … (头两项都是 1 ,此后每一项都是前两项之和)的相邻两项之比将会越来越接近黄金比例 0.618 ,不信请看: 1 / 1 = 1.0000000... 1 / 2 = 0.50000000... 2 / 3 = 0.66666667... 3 / 5 = 0.60000000

数据包分析之信息安全铁人三项

电子取证是指利用计算机软硬件技术,以符合法律规范的方式对计算机入侵.破坏.欺诈.攻击等犯罪行为进行证据获取.保存.分析和出示的过程.从技术方面看,计算机犯罪取证是一个对受侵计算机系统进行扫描和破解,对入侵事件进行重建的过程.具体而言,是指把计算机看作犯罪现场,运用先进的辨析技术,对计算机犯罪行为进行解剖,搜寻罪犯及其犯罪证据. 随着计算机犯罪个案数字不断上升和犯罪手段的数字化,搜集电子证据的工作成为提供重要线索及破案的关键.恢复已被破坏的计算机数据及提供相关的电子资料证据就是电子取证. 本文针对

求斐波那契数列的相邻两项的比值,精确到小数后三位。

未完成,只能假设知道是9和10代入. 代码如下: package zuoye; import java.math.BigDecimal; /* * 求斐波那契数列的相邻两项的比值,精确到小数后三位. * p1,p2,p3......pi,pj,...求pi/pj * 1 1 2 3 5 8 13 * 5/8,8/13,...收敛 */ public class Test { static double feibo(int x){ if(x==1||x==2) return 1; return f

两项基于网络的“黑客”技术

两项基于网络的“黑客”技术 软件开发和测试中网络环境迁移和数据分析 1   导读 关于智能路由器的这两项技术的介绍,想想还是搞个比较有噱头的标题才能吸引人进来细看的.本文确实是介绍的两个关于智能路由器的hack技术,但是却是将这两项技术用于软件开发过程中的正途中的.当然如果有悟性,也可以将此技术用于不违法的恶作剧玩一下也无妨. 本文提到的两个基于网络的hack技术: DNS劫持 数据抓包 基本本质上就是路由器设备的两个主要网络功能: 网关配置功能 网关数据中转功能 但是用于软件开发的正途,就变换

突破两项吉尼斯世界纪录浙江商帮全体高层应邀参观阿里巴巴

淘宝安全交易平台 www.xunjie36.com 淘宝店铺出售www.360feiyue.com 淘宝店铺交易www.360feiyue.com[关键词]淘宝安全交易平台 www.xunjie36.com 淘宝店铺出售www.360feiyue.com 淘宝店铺交易www.360feiyue.com淘宝小号信誉查询 此次阿里上市后的第一个"双十一",销售破百亿仅用38分钟:13个小时,成交额便已超过去年全天的销售总额362亿元:最终的成交额定格在571亿: 突破两项吉尼斯世界纪录,分