基环树:

把树再加一条边,形成一个环。
处理基环树的方法,一般就是找出环,并且把环当成广义上的树根,处理树,然后处理环。

一、AcWing 358. 岛屿:
你准备有限一个公园,该公园由 N 个岛屿组成,当地管理部门从每个岛屿出发向另外一个岛屿建了一座桥,不过桥是可以双向行走的。

同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。

相对于乘船而言,你更喜欢步行。

你希望所经过的桥的总长度尽可能的长,但受到以下的限制:

可以自行挑选一个岛开始游览。
任何一个岛都不能游览一次以上。
无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D。由S到D可以有以下方法:
(1)步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离中。
(2)渡船:你可以选择这种方法,仅当没有任何桥和以前使用过的渡船的组合可以由S走到D(当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。
注意,你不必游览所有的岛,也可能无法走完所有的桥。

请你编写一个程序,给定N座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的最大长度。

输入格式
第1行包含整数 N 。

第 2… N+1 行,每行包含两个整数 a 和 L,第 i+1 行表示岛屿 i 上建了一座通向岛屿 a 的桥,桥的长度为 L。

输出格式
输出一个整数,表示结果。

对某些测试,答案可能无法放进32-bit整数。

数据范围
2≤N≤106,
1≤L≤108
输入样例:
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3
输出样例:
24

题意:基环森林里面找每棵基环树的直径。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define llu unsigned ll
using namespace std;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int maxn=1000100;
int head[maxn],ver[maxn<<1],nt[maxn<<1],q[maxn<<1],du[maxn],c[maxn];
ll edge[maxn<<1],d[maxn],a[maxn<<1],b[maxn<<1],f[maxn];
int n,tot=1,cnt=0;

void add(int x,int y,ll z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
    du[y]++;
}

void bfs(int s)
{
    int l=1,r=1;
    q[1]=s,c[s]=cnt;
    while(l<=r)
    {
        int x=q[l];
        l++;

        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            if(c[y]) continue;
            q[++r]=y;
            c[y]=cnt;
        }
    }
}

void topsort(void)
{
    int l=1,r=0;
    for(int i=1;i<=n;i++)
        if(du[i]==1) q[++r]=i;
    while(l<=r)
    {
        int x=q[l];
        l++;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            ll z=edge[i];
            if(du[y]>1)
            {
                d[c[x]]=max(d[c[x]],f[x]+f[y]+z);
                f[y]=max(f[y],f[x]+z);
                du[y]--;
                if(du[y]==1) q[++r]=y;
            }
        }
    }
}

void dp(int cnt,int x)
{
    int m=0,l,r,y=x;
    int i;
    do
    {
        a[++m]=f[y],du[y]=1;
        for( i=head[y];i;i=nt[i])
        {
            if(du[ver[i]]>1)
            {
                y=ver[i];
                b[m+1]=b[m]+edge[i];
                break;
            }
        }
    }while(i);

    if(m==2)
    {
        for(int i=head[y];i;i=nt[i])
        {
            if(ver[i]==x)
                d[cnt]=max(d[cnt],f[x]+f[y]+edge[i]);
        }
        return ;
    }

    for(int i=head[y];i;i=nt[i])
    {
        if(ver[i]==x)
        {
            b[m+1]=b[m]+edge[i];
            break;
        }
    }

    for(int i=1;i<=m;i++) a[m+i]=a[i],b[m+i]=b[m+1]+b[i];

    l=1,r=1;
    q[1]=1;
    for(int i=2;i<=2*m;i++)
    {
        while(l<=r&&i-q[l]>=m) l++;
        d[cnt]=max(d[cnt],a[i]+a[q[l]]+b[i]-b[q[l]]);
        while(l<=r&&a[q[r]]-b[q[r]]<=a[i]-b[i]) r--;
        q[++r]=i;
    }

}

int main(void)
{
    int x,y;
    ll z;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%lld",&y,&z);
        add(i,y,z);
        add(y,i,z);
    }

    for(int i=1;i<=n;i++)
        if(!c[i]) cnt++,bfs(i);

    topsort();

    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        if(du[i]>1)
        {
            dp(c[i],i);
            ans+=d[c[i]];
        }
    }
    printf("%lld\n",ans);
    return 0;
}



二、AcWing 359. 创世纪:
上帝手中有 N 种世界元素,每种元素可以限制另外1种元素,把第 i 种世界元素能够限制的那种世界元素记为 A[i]。

现在,上帝要把它们中的一部分投放到一个新的空间中去建造世界。

为了世界的和平与安宁,上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素限制它。

上帝希望知道,在此前提下,他最多可以投放多少种世界元素?

输入格式
第一行是一个整数N,表示世界元素的数目。

第二行有 N 个整数A[1], A[2], …, A[N]。A[i] 表示第 i 个世界元素能够限制的世界元素的编号。

输出格式
一个整数,表示最多可以投放的世界元素的数目。

数据范围
N≤105,1≤A[i]≤N
输入样例:
6
2 3 1 3 6 5
输出样例:
3

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int maxn=100100;
int pre[maxn],head[maxn],ver[maxn],nt[maxn];
int f[maxn],du[maxn],dp[maxn][2],q[maxn];
int n,tot=1;

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}

int fi(int x)
{
    if(f[x]!=x)
        f[x]=fi(f[x]);
    return f[x];
}

vector<pair<int,int> >vc;

void now_point_dp(int x)
{
    if(!head[x])
    {
        dp[x][0]=0,dp[x][1]=-inf;
        return ;
    }

    dp[x][0]=0,dp[x][1]=1;
    bool flag=false;
    for(int i=head[x];i;i=nt[i])
    {
        int y=ver[i];
        int maxx=max(dp[y][0],dp[y][1]);
        if(dp[y][0]==maxx) flag=true;
        dp[x][0]+=maxx,dp[x][1]+=maxx;
    }
    if(!flag)
    {
        int minn=inf;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            minn=min(minn,dp[y][1]-dp[y][0]);
        }
        dp[x][1]-=minn;
    }
}

int main(void)
{
    int x;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        int ii=fi(i),xx=fi(x);
        if(ii!=xx)
        {
            pre[i]=x,f[ii]=xx,du[x]++;
            add(x,i);
        }
        else pre[i]=-1,vc.push_back(make_pair(i,x));
    }

    int l=1,r=0;
    for(int i=1;i<=n;i++) if(du[i]==0) q[++r]=i;
    //拓扑序dp
    while(l<=r)
    {
        int x=q[l];
        l++;
        now_point_dp(x);
        if(pre[x]!=-1)
        {
            du[pre[x]]--;
            if(du[pre[x]]==0) q[++r]=pre[x];
        }
    }
    //强制连接。
    int ans=0;
    for(vector<pair<int,int> >::iterator it=vc.begin();it!=vc.end();it++)
    {
        int x=it->first,y=it->second;
        int maxx=dp[x][1];
        int sum=1;
        for(int i=head[y];i;i=nt[i]) sum+=max(dp[ver[i]][0],dp[ver[i]][1]);
        if(dp[y][1]<sum)
        {
            dp[y][1]=sum;
            int nowx=pre[y];
            while(nowx!=-1)
            {
                int sum0=dp[nowx][0],sum1=dp[nowx][1];
                now_point_dp(nowx);
                if(sum0==dp[nowx][0]&&sum1==dp[nowx][1]) break;
                nowx=pre[nowx];
            }
        }
        maxx=max(maxx,dp[x][0]);
        ans+=maxx;
    }

    printf("%d\n",ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值