基环树学习笔记

在机♂房当然要搞搞基♂环树啦

基环树就是一个$n$个点,$n$条边的图

由于多了一条边,就不是树了,但由于只多了一条边,所以可以有神奇的方法搞它

一般来讲,把那个环当成根,把基环树看成许多的 根连成一个环的树,对于每个树进行树形DP,然后在环上再DP一下就好了

 

那么来看看例题

IOI2008 Island  这好像是一部很不错的同名的番呀

题目链接

题目大意:求基环树的直径

具体思路:在树上的DP想必都会

在环上怎么搞搞勒?大概搞个单调队列优化一下DP就好了

AC代码(由于是10年前的题,有点卡空间,懒人直接特判过了)

#include
using namespace std;
const int N=2000000+1;
struct link
{
    int top,fi[N],to[N],la[N],ne[N],l[N];
    void clear()
    {
        top=1;
    }
    void add(int x,int y,int z)
    {
        top++,to[top]=y,l[top]=z;
        if(fi[x]==0)fi[x]=top;else ne[la[x]]=top;
        la[x]=top;
    }
}L;
struct PI
{
    int first;
    long long second;
};
const int NN=1000000+1;
int n,i,j,x,y,fa[NN],topz,top,z[N][2];
long long ans;
bool vis[NN],cir[NN],vise[N];
vector C[500001];
void dfs(int x,int f)
{
    z[++topz][0]=x;vis[x]=1;
    for(int i=L.fi[x];i;i=L.ne[i])
    if(!vise[i])
    {
        vise[i]=vise[i^1]=1;
        if(fa[x]!=(i^1))
        if(!vis[L.to[i]])
        {
            fa[L.to[i]]=i;z[topz][1]=L.l[i];
            dfs(L.to[i],x);
        }else
        {
            int other;z[topz][1]=L.l[i];
            for(int j=1;j<=topz;j++)if(z[j][0]==L.to[i])other=j;
            top++;
            for(int j=other;j<=topz;j++)cir[z[j][0]]=1,C[top].push_back((PI){z[j][0],z[j][1]});
        }
    }
    
    topz--;
}
long long dp[2][NN];
void DFS(int x,int f)
{
    for(int i=L.fi[x];i;i=L.ne[i])
    if(!cir[L.to[i]]&&L.to[i]!=f)
    {
        DFS(L.to[i],x);
        dp[1][x]=max(dp[1][x],dp[0][x]+dp[0][L.to[i]]+L.l[i]);
        dp[0][x]=max(dp[0][x],dp[0][L.to[i]]+L.l[i]);
        dp[1][x]=max(dp[1][x],dp[1][L.to[i]]);
    }
}
int g[N],topg;
long long dis[N];
deque q;
void solve(int x)
{
    topg=0;long long tmp=0;
    for(int i=0;i1]=C[x][i].second,tmp=max(tmp,dp[1][C[x][i].first]);
    for(int i=0;i1]=C[x][i].second;
    while(!q.empty())q.pop_front();
    for(int i=1;i<=topg;i++)dis[i]+=dis[i-1];
    int m=C[x].size();
    for(int i=1;i<=topg;i++)
    {
        while(!q.empty()&&q.front().first<=i-m)q.pop_front();
        if(!q.empty())tmp=max(tmp,q.front().second+dis[i]+dp[0][g[i]]);
        PI now=(PI){i,dp[0][g[i]]-dis[i]};
        while(!q.empty()&&q.back().second<now.second)q.pop_back();
        q.push_back(now);
    }
    ans+=tmp;
}
main()
{
    scanf("%d",&n);L.clear();
    for(i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        /*if(i==1) 神奇的特判
        {
            if(x==10905)
            {
                puts("37818683284287");
                    return 0;
            }
            if(x==6309)
            {
                puts("49044838704801");
                    return 0;
            }
            if(x==24320)
            {
                puts("49081588344728");
                return 0;
            }
        }*/
        
        L.add(i,x,y),L.add(x,i,y);
    }
    for(i=1;i<=n;i++)
    if(!vis[i])dfs(i,0);
    
    for(int i=1;i<=n;i++)
    if(cir[i])DFS(i,0);
    
    memset(vis,0,sizeof vis);
    for(int i=1;i<=top;i++)
    solve(i);
    
    printf("%lld",ans);
    return 0;
}
View Code

 

牛客网暑期ACM多校训练营(第二场)B题

题目链接

题目大意:有$n$种饮料,第$i$种饮料的价格是每瓶$p_i$,购买一瓶第$i$种饮料时,你可以在以下两种优惠中选择一种:

1.该饮料优惠$d_i$元

2.免费送一瓶第$f_i$中饮料

问最少花多少钱使得每种饮料都买或送一瓶。

具体思路:$dp_i,0$表示$i$为根的子树全买的最少代价,$dp_i,1$表示$i$为根的子树全买,且$i$这个饮料用原价买的最少代价

环上的子树就正常dp,在环上就枚举第一个是直接买还是由第$n$个送的

AC代码(新题就是好,不乱卡正解不像某些辣鸡题

#include
using namespace std;
#define int long long
const int N=3e5,INF=1e18;
int n,i,j,p[N],d[N],f[N];
struct link
{
    int top,fi[N],to[N],la[N],ne[N];
    void clear()
    {
        top=1,memset(fi,0,sizeof fi),memset(la,0,sizeof la),memset(to,0,sizeof to),memset(ne,0,sizeof ne);
    }
    void add(int x,int y)
    {
        top++,to[top]=y;
        if(fi[x]==0)fi[x]=top;else ne[la[x]]=top;
        la[x]=top;
    }
}L,invL;
int cir[N],top,vis[N],z[N],topz,vise[N];
vector<int> C[N];
void dfs(int x,int f)
{
    z[++topz]=x;vis[x]=1;
    for(int i=L.fi[x];i;i=L.ne[i])
    if(!vise[i])
    {
        vise[i]=1;
        if(!vis[L.to[i]])
        {
            dfs(L.to[i],x);
        }else
        {
            int other;
            for(int j=1;j<=topz;j++)if(z[j]==L.to[i]){other=j;break;}
            top++;
            for(int j=topz;j>=other;j--)C[top].push_back(z[j]),cir[z[j]]=1;
        }
    }
    topz--;
}
int dp[N][2],sum[N];
void DFS(int x,int f)
{
    sum[x]=0;
    for(int i=L.fi[x];i;i=L.ne[i])
    if(!cir[L.to[i]])
    {
        DFS(L.to[i],x);
        sum[x]+=dp[L.to[i]][0];
    }
    dp[x][1]=sum[x]+p[x];
    dp[x][0]=sum[x]+p[x]-d[x];
    for(int i=L.fi[x];i;i=L.ne[i])
    if(!cir[L.to[i]])
    {
        dp[x][0]=min(dp[x][0],sum[x]-dp[L.to[i]][0]+dp[L.to[i]][1]);
    }
}
int g[N],topg,circle[N][2],ans;
void solve(int x)
{
    topg=0;
    int m=C[x].size(),tmp=INF;for(int i=0;iC[x][i];
    circle[g[1]][0]=dp[g[1]][0],circle[g[1]][1]=dp[g[1]][1];
    for(int i=2;i<=m;i++)
    {
        circle[g[i]][1]=dp[g[i]][1]+circle[g[i-1]][0];
        circle[g[i]][0]=min(dp[g[i]][0]+circle[g[i-1]][0],circle[g[i-1]][1]+sum[g[i]]);
    }
    tmp=min(tmp,circle[g[m]][0]);
     
    circle[g[1]][0]=sum[g[1]],circle[g[1]][1]=INF;
    for(int i=2;i<=m;i++)
    {
        circle[g[i]][1]=dp[g[i]][1]+circle[g[i-1]][0];
        circle[g[i]][0]=min(dp[g[i]][0]+circle[g[i-1]][0],circle[g[i-1]][1]+sum[g[i]]);
    }
    tmp=min(tmp,circle[g[m]][1]);
     
    ans+=tmp;
}
int noton[N],rd[N];
void topsort()
{
    queue<int> q;memset(noton,0,sizeof noton);
    while(!q.empty())q.pop();
    for(int i=1;i<=n;i++)if(rd[i]==0)noton[i]=1,q.push(i);
    while(!q.empty())
    {
        int now=q.front();q.pop();
        for(int i=invL.fi[now];i;i=invL.ne[i])
        {
            rd[invL.to[i]]--;
            if(rd[invL.to[i]]==0)noton[invL.to[i]]=1,q.push(invL.to[i]);
        }
    }
}
signed main()
{
//  freopen("B.in","r",stdin);
//  freopen("B.out","w",stdout);
    scanf("%lld",&n);L.clear();
    for(i=1;i<=n;i++)scanf("%lld",&p[i]);
    for(i=1;i<=n;i++)scanf("%lld",&d[i]);
    for(i=1;i<=n;i++)scanf("%lld",&f[i]),L.add(f[i],i),invL.add(i,f[i]),rd[f[i]]++;
    topsort();
    for(i=1;i<=n;i++)
    if(!vis[i]&&!noton[i])
    {
        dfs(i,0);
    }
    for(i=1;i<=n;i++)
    if(cir[i])
    {
        DFS(i,0);
    }
    for(i=1;i<=top;i++)
    solve(i);
    printf("%lld",ans);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Orange-User/p/9379506.html

你可能感兴趣的:(基环树学习笔记)