[IOI2008]Island

题目大意:

找基环树直径

(这个题输入给出的是内向基环树(虽然是无向边))

存在两种情况:

1.直径在树上。

2.直径从树里走到环上,再走进另外一个树里。

首先dfs找到环。

第一种直接树形dp。dp[i]i往下最长路径。还能用来求第二种情况。

第二种,找到环之后,断环成链,复制一倍。求的是,选择距离小于环长的两个点,贡献是两个点的dp[i],和两个点之间的距离。这个用单调队列优化dp即可。

bzoj会爆栈mmp

如果你是ywy可以bfs求树形dp

其实,这是一个内向基环树,所以,直接topo排序,从叶子往上面topo

即可求得dp,还能求得环。

代码:(dfs爆栈版)

#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^‘0‘)
using namespace std;
typedef long long ll;
void rd(int &x){
    char ch;x=0;
    while(!isdigit(ch=getchar()));
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
}
namespace Miracle{
const int N=1e6+5;
int n;
struct node{
    int nxt,to;
    ll val;
}e[2*N];
int hd[N],cnt=1;
void add(int x,int y,int z){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    e[cnt].val=z;
    hd[x]=cnt;
}
bool vis[N];
bool on[N];//on the huan
bool in[N];
int to[N],v[N];
int sta[N],top;
int len;//huan chang
int mem[2*N],tot;
ll cos[2*N];
int q[2*N],l,r;
ll f[N];
ll ans;
ll sum;
ll zhi;
bool fl;
void dfs(int x,int in_edge){
//    cout<<" dfs "<<x<<" "<<fl<<" "<<top<<endl;
    vis[x]=1;
    sta[++top]=x;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(i==(in_edge^1)) continue;
        if(!vis[y]){
            dfs(y,i);
        }
        else if(!fl){
            fl=true;
        //    cout<<" find "<<top<<" : "<<sta[top]<<endl;
            int z;
            do{
                z=sta[top];
                on[z]=1;
                mem[++len]=z;
                top--;
            }while(z!=y);
        }
    }
    if(sta[top]==x) --top;//warning!!!
}
void dp(int x,int fa){//find farthest
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(on[y]) continue;
        dp(y,x);
        zhi=max(zhi,f[x]+e[i].val+f[y]);
        f[x]=max(f[x],e[i].val+f[y]);
    }
}
ll wrk(int st){
    sum=0;
    len=0;
    fl=false;top=0;
    dfs(st,0);

    for(reg i=1;i<=len;++i){
        zhi=0;
        dp(mem[i],0);
        sum=max(sum,zhi);
        mem[i+len]=mem[i];
    }
    l=1,r=0;
    cos[1]=0;
    for(reg i=2;i<=2*len;++i){
        cos[i]=(to[mem[i]]==mem[i-1])?v[mem[i]]:v[mem[i-1]];
        cos[i]+=cos[i-1];
    }
    for(reg i=1;i<=2*len;++i){
        while(l<=r&&i-q[l]>=len) ++l;
        if(i!=1) sum=max(sum,f[mem[q[l]]]-cos[q[l]]+cos[i]+f[mem[i]]);
        while(l<=r&&f[mem[q[r]]]-cos[q[r]]<=f[mem[i]]-cos[i]) --r;
        q[++r]=i;
    }
    return sum;
}
int main(){
    rd(n);int x;int z;
    for(reg i=1;i<=n;++i){
        rd(x);rd(z);
        to[i]=x;
        v[i]=z;
        add(i,x,z);add(x,i,z);
    }
    for(reg i=1;i<=n;++i){
        if(!vis[i]){
            ans+=wrk(i);
        }
    }
    printf("%lld",ans);
    return 0;
}

}
int main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/11/4 9:27:26
*/

原文地址:https://www.cnblogs.com/Miracevin/p/9903327.html

时间: 2024-10-09 15:21:59

[IOI2008]Island的相关文章

[题解] LuoguP4381 [IOI2008]Island

LuoguP4381 [IOI2008]Island Description 一句话题意:给一个基环树森林,求每棵基环树的直径长度的和(基环树的直径定义与树类似,即基环树上一条最长的简单路径),节点总数不超过\(10^6\). Solution 问题就是如何求基环树的直径. 首先树的直径的话可以直接\(dp\),那如果有一个环怎么办? 这个环上会挂着几棵树,那么直径只会有两种情况 不经过环上的边,即每棵树直径的最大值 经过一个环,即挂在换上的两棵树\(i,j\)的深度和在加上\(i,j\)在环上

【BZOJ 1791】 [Ioi2008]Island 岛屿

Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船. 相对于乘船而言,你更喜欢步行.你希望所经过的桥的总长度尽可能的长,但受到以下的限制. • 可以自行挑选一个岛开始游览. • 任何一个岛都不能游览一次以上. • 无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D.由S到D可以有以下方法: o 步

bzoj 1791: [Ioi2008]Island 岛屿【基环树+单调队列优化dp】

我太菜了居然调了一上午-- 这个题就是要求基环树森林的基环树直径和 大概步骤就是找环->dp找每个环点最远能到达距离作为点权->复制一倍环,单调队列dp 找环是可以拓扑的,但是利用性质有更快好写的做法,就是像朱刘算法找环那样,按照输入的方向(i->to_i)打一圈标记,如果碰到同样标记就说明有环,这里注意我一开始没注意到的,从i点进入找到环不代表i点在环上,因为可能是6字形的,所以一定是环点的是找到的有同样标记的那个点,然后顺着这个点把环点都放进一个栈(其实不用,但是这样好写一些),顺着

P4381 [IOI2008]Island

传送门 显然题目给的图构成一个基环树 对于每个基环树单独考虑,显然每个都走直径是最优的 考虑如何求出基环树的直径 把直径分为两种情况考虑,首先可以找出环 因为直径可能不在环边上,所以对每个环上节点的子树进行一遍 $dfs$,求出每个节点子树的直径 维护 $dis[x]$ 表示节点 $x$ 到叶子节点的最长路程,那么直径就是每个节点儿子的 $dis$ 中最大和次大的和 可以一遍循环动态维护最大和次大 直径也可能在环上 设环上两点 $x,y$ 的距离为 $d(x,y)$,那么就是求最大的 $dis[

IOI2008 Island 岛屿

题目描述: bz luogu 题解: 裸的基环树直径. 代码: #include<queue> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 1000050; template<typename T> inline void read(T&x) { T f = 1

[bzoj1791][ioi2008]Island 岛屿(基环树、树的直径)

bzoj luogu 题意可能会很绕 一句话:基环树的直径. 求直径: 对于环上每一个点记录其向它的子树最长路径为$dp_x$ 之后记录环上边长前缀和$ns_i$ dp值为$max_{i,j}dp[i]+sum[i]+dp[j]-sum[j]$ $dp[j]-sum[j]$提出来进单调队列. O(n). 记得dfs改bfs. #include<cstdio> #include<algorithm> using namespace std; typedef long long lin

岛屿(bzoj1791)

1791: [Ioi2008]Island 岛屿 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 2042  Solved: 461[Submit][Status][Discuss] Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船. 相对于乘船而言,你更喜欢

463 Island Perimeter

You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one isla

Leetcode-463 Island Perimeter

#463. Island Perimeter You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and th