【BZOJ1791】【IOI2008】【基环树】island(status速度第一)





1791: [Ioi2008]Island 岛屿 

Time Limit: 20 Sec  Memory Limit: 162 MB
Submit: 908  Solved: 159
[Submit][Status]

Description

你将要游览一个有N个岛屿的公园。从每一个岛i出发,只建造一座桥。桥的长度以Li表示。公园内总共有N座桥。尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走。同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船。 相对于乘船而言,你更喜欢步行。你希望所经过的桥的总长度尽可能的长,但受到以下的限制。 • 可以自行挑选一个岛开始游览。 • 任何一个岛都不能游览一次以上。 • 无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D。由SD可以有以下方法: 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离;或者 渡船:你可以选择这种方法,仅当没有任何桥和/或以前使用过的渡船的组合可以由S走到D(当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。 注意,你不必游览所有的岛,也可能无法走完所有的桥。 任务 编写一个程序,给定N座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的最大长度。 限制 2 <= N <= 1,000,000 公园内的岛屿数目。 1<= Li <= 100,000,000 i的长度。 

Input

• 第一行包含N个整数,即公园内岛屿的数目。岛屿由1N编号。 • 随后的N行每一行用来表示一个岛。第行由两个以单空格分隔的整数,表示由岛i筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度Li。你可以假设对於每座桥,其端点总是位于不同的岛上。 

Output

你的程序必须向标准输出写出包含一个整数的单一行,即可能的最大步行距离。 注1:对某些测试,答案可能无法放进32-bit整数,你要取得这道题的满分,可能需要用Pascalint64C/C++long long类型。 注2:在比赛环境运行Pascal程序,由标准输入读入64-bit数据比32-bit数据要慢得多,即使被读取的数据可以32-bit表示。我们建议把输入数据读入到32-bit数据类型。 评分 N不会超过4,000。 

Sample Input

7
 3 8
 7 2
 4 2
 1 4
 1 9
 3 4
 2 3

Sample Output

24

/******************************************************************************/

题意:

输入n,然后n行第i行是由i向外引一条无向边,以及输入边的权值,这样会组成k个联通图,每个图上取两点,使其间路径长度最大,求这些“最大路径”长度总和。

 

题目分析:

首先一个n点联通图上会有n条边,而它又联通,所有它一定是有环的,因为有n-1条边的n点联通图保证联通且无环,剩下一条边则一定使“树”中有且只有一个环,于是得证每个联通图都是一个“基环树”,即有且仅有一个环的树。

那么现在我们应该输出的就是所有基环树的直径总和(树的直径即树上点不重复的最长路径)。

 

如图,这棵基环树的直径就是11到5,经过3,长度71。

题解:

我们需要对每个基环树单独处理,先搜到它的环,然后dfs求每个环上点往外枝杈的最长路径,这样每个点就有了一个权值,然后环上每个边又有了一个权值,就可以在环上做一发单调队列动规,求基环树直径长度啦!

当然,我们对于每棵树的直径是在动规里面定论的,不过它的初值却不能在动规前赋成0

如下图中,直径就不经过环,为136,长度79,所以在dfs求环上点权的,也不能忘了更新直径值,不过怎么更新就需要略加斟酌了。

 

实现:

首先双向建边,然后对任意点搜索,搜到某个点已经搜过了,就代表这个点是环上的,然后处理环,再然后就是搜索找到每个点,然后递归出枝杈边权和最长值,作为点权值,进行单调队列优化后的动规,过程中更新直径长度,单调队列把点倍增,然后两点间长度小于环长度即可。

我在此证明一下动规的单调性。【甚水,可略过】首先设点ABCD依次排列,则从AD(有向,不能从DA)的长度为环上AD的长度和加上AD点权和。不要问我为什么不能从DA,因为我已经把点倍增了,从D到第二个A就是“从DA”了。

好吧,跑题了,我来总结一下,

长度AD = A点权+A~B边长+B~C边长+C~D边长+D点权

那么长度BD就 = B点权+B~C边长+C~D边长+D点权

发生了什么?什么?只要B点权 > A点权+A~B边长,那么从BD就比AD优!同理,后面的一样。

结果出来了,然后输出即可,注意输入要用int,输出用long long,不然会超时,超很多,IOI2008测试数据中isl18f.in秒出解,但是用long long 读入,呵呵呵呵。

时间丶代码复杂度优化:

找环实现很烦很磨叽对不对?这时候我们可以引入一种新的解决方法,类似于最小树形图找环的实现(上文的实现方法我其实AC前并不知道)。

目前BZOJ排名第一你怕不怕?常数再好好写写还能更快你怕不怕?就问你怕不怕?

这里就需要运用到本题的一些性质。

首先我们可以把数据输入时的边方向当成正方向,其反方向,好吧,“当成”反方向。这样每个点都有且仅有一条出边。然后我们从任意一个点开始遍历,如果没有环,那是不是根本停不下来?!而数据又是有限的,所以最后就一定会连到已有的链上的某点,而很好想,这个点向前遍历,最后会回到这个点上,即它是一个环,而之前的点的出边方向都是在向环推进。这时候我们再把其它点挨个遍历,首先同一个联通图是一定不会有俩环的,所以要想改变“根本停不下来”的现状,新点遍历到最后,就一定会指向某个已经遍历过的点,从而连上了环,或者方向是向环的。这样我们可以把数据输入的方向存为next[i],然后把长度存为len[i]。而反向边则有邻接表存储(讨厌指针的我写的是链式前向星)。

这样我们只需要遍历任意点,通过next标记沿途所有点,然后遍历到一个已标记点,即可以高速找到基环树的环了,然后我们对环上每个点进行“-1”标记,禁止接下来的dfs找到该点(反边存了环上的边),从而可以再接下来的dfs中顺利给环上每个点赋值(找枝杈,递归求最长长度)。

优化到此结束,接下来的单调队列与一般做法无异了。

注意:

本题dfs递归出解时由于层数过深可能爆系统栈,所以需要写一份非递归版自写栈的dfs,我会在后面代码附上注释掉的递归版dfs以利于理解思想。(不要想着开大系统栈!那还不如手写非递归dfs呢!)

/**************************************************************
    Problem: 1791
    User: 18357
    Language: C++
    Result: Accepted
    Time:11052 ms
    Memory:120100 kb
****************************************************************/
 
#include 
#include 
#include 
#include 
#include 
#define N 1050000
using namespace std;
struct Syndra
{
    int u,v,len,next;
}e[N];
struct Fiona
{
    int edge,flag1,flag2;
    long long temp,max1,max2;
}s[N];
int head[N],cnt,n;
int visit[N],next[N],len[N];
int i,j,k;
long long sa[N],pre[N],ans;
void add(int u,int v,int len)
{
    cnt++;
    e[cnt].u=u;
    e[cnt].v=v;
    e[cnt].len=len;
    e[cnt].next=head[u];
    head[u]=cnt;
}
int que[N<<1];
long long sum[N<<1],ret;
long long dp(int num)
{
    int top,tail;
    int u,b,star;
    int et;
    for(et=1;et<(num<<1);et++)
    {
        sum[et]=sum[et-1]+pre[(et-1)>=num?(et-1-num):(et-1)];
    }
    top=tail=0;
     
    que[top]=0;
    for(et=1;et<(num<<1);et++)
    {
        while(et-que[top]>=num)top++;
        u=que[top];
        ret=max(ret,sa[et>=num?et-num:et]+sa[u>=num?u-num:u]+sum[et]-sum[u]);
        b=que[tail];
        que[++tail]=et;
        for(star=tail;star>top;b=que[star-1])
        {
            if(sum[et]-sum[b]+sa[b>=num?b-num:b]=num?et-num:et])
            {
                que[star]=b;
                que[--star]=et;
            }
            else break;
        }
        tail=star;
    }
     
    /*que[tail++]=0;
    for(et=1;et<(num<<1);++et)
    {
        while(top=num)++top;
        u=que[top];
        ret=max(ret,sa[et>=num?et-num:et]+sa[u>=num?u-num:u]+sum[et]-sum[u]);
        while(top=num?et-num:et]>=sa[que[tail-1]>=num?que[tail-1]-num:que[tail-1]]+sum[et]-sum[que[tail-1]])--tail;
        que[tail++]=et;
    }*/
    return ret;
}
void build()
{
    cnt=1;
    memset(head,0,sizeof(head));
    memset(visit,0,sizeof(visit));
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d%d",&next[i],&len[i]);
        add(next[i],i,len[i]);
    }
}
stacksk;
int fa[N];
void dfs(int x)
{
    if(s[x].edge==0)
    {
        sk.pop();
        if(s[x].flag2)ret=max(ret,s[x].max1+s[x].max2);
        if(visit[x]==-1)
            return ;
        x = sk.top();
        {
            int v,tt=s[x].edge;
            v=e[tt].v;
            visit[v]=i;
            s[x].temp=s[v].max1+e[tt].len;
            if(s[x].max1
下面是读入优化后的代码,这年头,你不读入优化,光输入就得比这代码慢啊~
 
  
/**************************************************************
    Problem: 1791
    User: 18357
    Language: C++
    Result: Accepted
    Time:4556 ms
    Memory:120132 kb
****************************************************************/
 
#include 
#include 
#include 
#include 
#include 
#include 
#define N 1050000
using namespace std;
inline int getc() {
    static const int L = 1<<15;
    static char buf[L],*S=buf,*T=buf;
    if(S==T){
        T=(S=buf)+fread(buf,1,L,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
inline int getint() {
    int c;
    while(!isdigit(c = getc()));
    int tmp = c-'0';
    while(isdigit(c=getc()))
        tmp=(tmp<<1)+(tmp<<3)+c-'0';
    return tmp;
}
struct Syndra
{
    int u,v,len,next;
}e[N];
struct Fiona
{
    int edge,flag1,flag2;
    long long temp,max1,max2;
}s[N];
int head[N],cnt,n;
int visit[N],next[N],len[N];
int i,j,k;
long long sa[N],pre[N],ans;
void add(int u,int v,int len)
{
    cnt++;
    e[cnt].u=u;
    e[cnt].v=v;
    e[cnt].len=len;
    e[cnt].next=head[u];
    head[u]=cnt;
}
int que[N<<1];
long long sum[N<<1],ret;
long long dp(int num)
{
    int top,tail;
    int u,b,star;
    int et;
    for(et=1;et<(num<<1);et++)
    {
        sum[et]=sum[et-1]+pre[(et-1)>=num?(et-1-num):(et-1)];
    }
    top=tail=0;
    /*
    que[top]=0;
    for(et=1;et<(num<<1);et++)
    {
        while(et-que[top]>=num)top++;
        u=que[top];
        ret=max(ret,sa[et>=num?et-num:et]+sa[u>=num?u-num:u]+sum[et]-sum[u]);
        b=que[tail];
        que[++tail]=et;
        for(star=tail;star>top;b=que[star-1])
        {
            if(sum[et]-sum[b]+sa[b]=num)++top;
        u=que[top];
        ret=max(ret,sa[et>=num?et-num:et]+sa[u>=num?u-num:u]+sum[et]-sum[u]);
        while(top=num?et-num:et]>=sa[que[tail-1]>=num?que[tail-1]-num:que[tail-1]]+sum[et]-sum[que[tail-1]])--tail;
        que[tail++]=et;
    }
    return ret;
}
void build()
{
    cnt=1;
    memset(head,0,sizeof(head));
    memset(visit,0,sizeof(visit));
    n=getint();
    for(i=1;i<=n;i++)
    {
        next[i]=getint();
        len[i]=getint();
        add(next[i],i,len[i]);
    }
}
stacksk;
int fa[N];
void dfs(int x)
{
    if(s[x].edge==0)
    {
        sk.pop();
        if(s[x].flag2)ret=max(ret,s[x].max1+s[x].max2);
        if(visit[x]==-1)
            return ;
        x = sk.top();
        {
            int v,tt=s[x].edge;
            v=e[tt].v;
            visit[v]=i;
            s[x].temp=s[v].max1+e[tt].len;
            if(s[x].max1

你可能感兴趣的:(找环,基环树)