[雅礼集训6-23] T1 电报

每个点都只连向一个点,所以最终的图就是若干个基环树,原问题等价于在基环树中选出若干条互不相交的链,使链顶权值和最小,对每个基环树DP即可。
树型DP:设dp[i]表示点i及其子树中的答案,mx[i]表示max{c[son]},son为i的儿子,那么dp[i]=∑(dp[son]-mx[i])+c[i]。复杂度O(n)
环上DP:先枚举环的起始点s,且s->pre(c)的边断掉。设f[i]为s~i的答案和,那么f[i]=min{f[i-1],f[pre(i)]+mx[pre(i)]-c[i]}+dp[i],前一种情况表示从i和pre(i)的边断掉,即从i处重开一条链;后一种情况表示不断,那么pre(i)的子树就要与环上的主链断开。复杂度O(n^2)。还可以优化到O(n)。
O(n^2)做法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=100100;
int n,top,to[maxn],huan[maxn];
bool visit[maxn],pd,b[maxn];
ll ans=0,now,minf,c[maxn],dp[maxn],mx[maxn],f[maxn];
struct edge
{
    int t;
    ll w;
    edge *next;
}*con[maxn];
bool tepan()
{
    memset(visit,0,sizeof(visit));
    int p=1;
    while(!visit[p]){visit[p]=1;p=to[p];}
    for(int i=1;i<=n;i++)
        if(!visit[i]) return 0;
    return 1;
}
void ins(int x,int y)
{
    edge *p;
    p=new edge;
    p->t=y;
    p->next=con[x];
    con[x]=p;
}
void find(int v)
{
    visit[v]=1;
    if(!visit[to[v]]) find(to[v]);
    else pd=1;
    if(pd) {huan[++top]=v;b[v]=1;}
    if(to[huan[1]]==v) pd=0;
    visit[v]=0;
}
void dfs(int v)
{
    visit[v]=1;
    mx[v]=0;
    dp[v]=c[v];
    for(edge *p=con[v];p!=NULL;p=p->next)
        if(!b[p->t])
        {
            dfs(p->t);
            mx[v]=max(mx[v],c[p->t]);
            dp[v]+=dp[p->t];        
        }
    dp[v]-=mx[v];
}
int p(int x){return (x==1)?top:(x-1);}
int s(int x){return (x==top)?1:(x+1);}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%lld",&to[i],&c[i]);
        ins(to[i],i);
    }
    if (tepan()) ans=0;
    else
    {
        memset(b,0,sizeof(b));
        memset(dp,0,sizeof(dp));
        memset(visit,0,sizeof(visit));
        for(int i=1;i<=n;i++)
            if(!visit[i])
            {
                top=0;
                now=1e15;
                find(i);        
                for(int j=1;j<=top;j++)
                    dfs(huan[j]);
                for(int j=1;j<=top;j++)
                {
                    f[j]=dp[huan[j]];
                    for(int k=s(j);k!=j;k=s(k))
                        f[k]=min(f[p(k)]+dp[huan[k]],f[p(k)]+mx[huan[p(k)]]+dp[huan[k]]-c[huan[k]]);
                    now=min(now,f[p(j)]);
                }
                ans+=now;
            }   

    }
    printf("%lld\n",ans);   
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值