【BZOJ 1791】 [Ioi2008]Island 岛屿

1791: [Ioi2008]Island 岛屿 

Time Limit: 20 Sec   Memory Limit: 162 MB
Submit: 1027   Solved: 184
[ Submit][ Status]

Description

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

Input

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

Output

你的程序必须向标准输出写出包含一个整数的单一行,即可能的最大步行距离。 注1:对某些测试,答案可能无法放进32-bit整数,你要取得这道题的满分,可能需要用Pascal的int64或C/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

HINT


这道题就是求出来每一棵基环树的直径加起来。


对于每一棵基环树,先找见环。


直径可能在环上以某一点为根的子树里,也可能横跨两个子树中。


修改过程:

1.忘了考虑直径可能在以某一个点为根的子树里。


2.中间变量没有用long long


3.现在一直都是command terminated,就是在findcir中间就停不下来了。。



#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#define LL long long
#define M 1000005
using namespace std;
LL ans=0;
int all;
struct edge
{
    int y,ne;
    LL l;
}e[3*M];
int p,h[M],v[M],Link[M],pos[M],root,now,n,ok[M],fa[M],c[M],cir[M],L,R,tot,num;
LL dep[M],depest,d[M],pre[M],q[M],l[M];
void Addedge(int x,int y,LL l)
{
    tot++;
    e[tot].y=y;
    e[tot].ne=h[x];
    e[tot].l=l;
    h[x]=tot;
}
void Clear_v(int x)
{
    v[x]=0;
    for (int i=h[x];i;i=e[i].ne)
    {
        int y=e[i].y;
        if (v[y])
            Clear_v(y);
    }
}
void Findcir(int x)
{
    v[x]=++now;
    for (int i=h[x];i;i=e[i].ne)
    {
        int y=e[i].y;
        if (!v[y]) 
		{
			fa[y]=x;
			Findcir(y);
		}
        else if (y!=fa[x]&&v[y]<v[x])
        {
            cir[++num]=y;
            c[y]=1;
            while (x!=y)
                c[x]=1,cir[++num]=x,x=fa[x];
            return;
        }
    }
}
void Dfs(int x,LL dep)
{
    v[x]=1;
    for (int i=h[x];i;i=e[i].ne)
    {
        int y=e[i].y;
        if (!v[y]&&(!c[y]||y==root))
        {
            if (dep+e[i].l>depest) depest=dep+e[i].l,p=y;
            Dfs(y,dep+e[i].l);
        }
    }
}
void Getdep(int x,LL dep)
{
    v[x]=1;
    for (int i=h[x];i;i=e[i].ne)
    {
        int y=e[i].y;
        if (v[y]||c[y]) continue;
        if (depest<dep+e[i].l) depest=dep+e[i].l;
        Getdep(y,dep+e[i].l);
    }
}
LL Getdis(int x,int y)
{
    if (ok[x]&&Link[x]==y) return l[x];
    return l[y];
}
void Dp()
{
    depest=0LL;
    L=1,R=0;
    q[++R]=dep[2]+Getdis(cir[1],cir[2]);
    pos[R]=2;
    pre[2]=Getdis(cir[1],cir[2]);
    for (int i=3;i<=num;i++)
    {
        pre[i]=pre[i-1]+Getdis(cir[i-1],cir[i]);
        while (R&&pre[i]+dep[i]>=q[R])
            R--;
        q[++R]=pre[i]+dep[i];
        pos[R]=i;
    }
    depest=q[L]+dep[1];
    for (int i=2;i<=num;i++)
    {
        int add=i+num-1;
        pre[add]=pre[add-1]+Getdis(cir[add-1],cir[add]);
        if (pos[L]==i) L++;
        LL neww=pre[add]+dep[add];
        while (R>=L&&neww>=q[R])
            R--;
        q[++R]=neww;
        pos[R]=add;
        if (q[L]+dep[i]-pre[i]>depest)
            depest=q[L]+dep[i]-pre[i];
    }
}
void Solve(int x)
{
    now=0;
    num=0;
    Findcir(x);
    Clear_v(x);
    ans=0LL;
    if (num)
    {
        for (int i=1;i<=num;i++)
        {
            depest=0LL;
            root=cir[i];
            p=cir[i];
            Dfs(p,0LL);
            Clear_v(p);
            depest=0LL;
            Dfs(p,0LL);
            if (depest>ans) ans=depest;
        }
        Clear_v(x);
        for (int i=1;i<=num;i++)
        {
            depest=0LL;
            Getdep(cir[i],0LL);
            dep[i]=depest;
        }
        for (int i=1;i<=num;i++)
            dep[i+num]=dep[i],cir[i+num]=cir[i];
        for (int i=1;i<=num;i++)
            c[cir[i]]=0;
        Dp();
        if (depest<ans) depest=ans;
    }
    else
    {
        p=x;
        root=0;
        depest=0LL;
        Dfs(x,0LL);
        Clear_v(x);
        depest=0LL;
        Dfs(p,0LL);
    }
}
int main()
{
    freopen("isl.in","r",stdin);freopen("isl.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        ok[i]=1;
        v[i]=c[i]=0;
        scanf("%d%lld",&Link[i],&l[i]);
    }
    for (int i=1;i<=n;i++)
        if (ok[i]&&Link[Link[i]]==i)
        {
            if (l[i]<l[Link[i]]) ok[i]=0;
            else ok[Link[i]]=0;
        }
    for (int i=1;i<=n;i++)
        if (ok[i])
            Addedge(i,Link[i],l[i]),Addedge(Link[i],i,l[i]);
    LL ans=0LL;
    for (int i=1;i<=n;i++)
        if (!v[i])
            Solve(i),ans+=depest,cout<<depest<<endl;
    printf("%lld\n",ans);
    return 0; 
}


最终还是没有找到我的算法中哪有问题,于是看了lyd的代码,非常短也很好理解。


首先找到所有的连通分量。


然后对于每一个连通分量,通过拓扑排序直接把子树处理完,并且找到环的(环上的点的度数永远不可能变成0),同时也处理了直径可能不在环上的情况。


方法是:

对于一个点y,f[y]表示以y为根的子树的最长长度,用儿子x来更新y。


在更新y之前先用f[x]+f[y]+1更新答案(不包含环的树的直径),因为此时f[y]没有被f[x]更新,他一定是通过别的儿子更

新的,然后再用f[x]更新y。


这样就处理完了环上的每个点子树的最长长度,接下来拆环为链,用单调队列来做即可。


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#define M 1000005
#define LL long long
using namespace std;
struct edge
{
	int y,ne,v;
}e[M*2];
int h[M],v[M],c[M],du[M],q[2*M],n,m,tot,t;
LL f[M],d[M],a[2*M],b[2*M];
void Addedge(int x,int y,int v)
{
	tot++;
	e[tot].y=y;
	e[tot].ne=h[x];
	h[x]=tot;
	e[tot].v=v;
	du[x]++;
}
void dfs(int x,int k)
{
	v[x]=1,c[x]=k;
	for (int i=h[x];i;i=e[i].ne)
	{
		int y=e[i].y;
		if (v[y]) continue;
		dfs(y,k);
	}
}
void Topsort()
{
	int l=1,r=0,y;
	for (int i=1;i<=n;i++)
		if (du[i]==1) q[++r]=i;
	while (l<=r)
	{
		int x=q[l];
		for (int i=h[x];i;i=e[i].ne)
			if (du[y=e[i].y]>1)
			{
				du[y]--;
				d[c[x]]=max(d[c[x]],f[x]+f[y]+e[i].v);
				f[y]=max(f[y],f[x]+e[i].v);
				if (du[y]==1) q[++r]=y;
			}
		l++;
	}
}
void Dp(int t,int x)
{
	int m=0,i,y=x;
        do
	{
		a[++m]=f[y],du[y]=1;
		for (i=h[y];i;i=e[i].ne)
			if (du[e[i].y]>1)
			{
				y=e[i].y;
				b[m+1]=b[m]+e[i].v;
				break;
			}
	}while (i);
	if (m==2)
	{
		int l=0;
		for (int i=h[y];i;i=e[i].ne)
			if (e[i].y==x) l=max(l,e[i].v);
		d[t]=max(d[t],f[x]+f[y]+l);
		return;
	}
	for (int i=h[y];i;i=e[i].ne)
		if (e[i].y==x)
		{
			b[m+1]=b[m]+e[i].v;
			break;
		}
	for (int i=1;i<=m;i++)
	{
		a[m+i]=a[i];
		b[m+i]=b[m+1]+b[i];
	}
	int l,r;
	q[l=r=1]=1;
	for (int i=2;i<2*m;i++)
	{
		while (l<=r&&i-q[l]>=m)
			l++;
		d[t]=max(d[t],a[i]+a[q[l]]+b[i]-b[q[l]]);
		while (l<=r&&a[q[r]]+b[i]-b[q[r]]<=a[i])
			r--;
		q[++r]=i;
	}
}
int main()
{
        scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Addedge(x,i,y);
		Addedge(i,x,y);
	}
	memset(v,0,sizeof(v));
	t=0;
	for (int i=1;i<=n;i++)
		if (!c[i]) dfs(i,++t);
	Topsort();
	LL ans=0LL;
	memset(v,0,sizeof(v));
	for (int i=1;i<=n;i++)
		if (du[i]>1&&!v[c[i]])
		{
			v[c[i]]=1;
			Dp(c[i],i);
			ans+=d[c[i]];
		}
	cout<<ans<<endl;
	return 0;
}



感悟:

1.拓扑序来找环的方法很巧妙


2.拆环为链,用单调队列优化dp


3.lyd博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值