[JZOJ100003]【NOI2017模拟.4.1】 Tree

题目描述

这里写图片描述

这里写图片描述

分析

树链:一条从上到下的树上简单路径
对于另外20%:
由于di=1,那么设f[i]为做完i之后,即i的整颗子树都不能再选了的最大收益,那么每次在树链顶时,更新一下f[i],就是把整条路的其他无关的点的f统计起来。为了方便把边拆成点。
100%:
一般来说,这种玄学图论问题,想不到解法要往网络流上想想。那么这道题可能是一个最大费用流。
但是流一次要强制走一段路径,怎么办呢?考虑这样建图:原图G1的边,在新的图G2中,让深度较小的点连向较大的点,费用0,流量就是覆盖次数。然后一条树链的话,就从深度最大的点连一条边到最小的点,费用c,流量1。那么原来一次覆盖就对应了一个环,跑一个最大费用循环流就可以了。

无源汇最大费用流

在这个题设下,我们流完以后,所有点的度数为0,即每次流一个环出来。
做法:
首先让正权边全部流满。
开设一个数组du[]来记录每个节点的流量情况。
du[i]=in[i](i节点所有正权入流之和)-out[i](i节点所有正权出流之和)。
由于要保证流量平衡
将du[i]<0的连s->i,费用为0,流量为[0,-du[i]]的边。
将du[i]>0的连i->t,费用为0,流量为[0,du[i]]的边。
对于原图的u->v,cost,[0,up]
Cost>0,连v->u,cost,[0,up]
Cost<0,连u->v,-cost,[0,up]
Cost=0,照样
然后对新图做最小费用流,答案就是正边之和-第二次的费用。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<bitset>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=1000005;
struct rec
{
    int x,y,z;
}lu[N];
int t1,b1[N],c1[N],e1[N],next1[N],first1[N],dep[N],du[N];
int tt,b[N],c[N],inv[N],next[N],first[N],pd[N],now[N];
int n,m,x,y,z,i,j,k,T,st,en,ttt;
ll cost[N],ans,dis[N];
void cr1(int x,int y,int z)
{
    t1++;
    b1[t1]=y;
    c1[t1]=z;
    e1[t1]=x;
    next1[t1]=first1[x];
    first1[x]=t1;
}
void dfs(int x,int y)
{
    dep[x]=dep[y]+1;
    //fa[x]=y;
    for(int p=first1[x];p;p=next1[p])
        if (b1[p]!=y)
            dfs(b1[p],x);
}
void cr(int x,int y,int inv1,int cost1,int z)
{
    tt++;
    b[tt]=y;
    cost[tt]=cost1;
    c[tt]=z;
    inv[tt]=inv1+tt;
    next[tt]=first[x];
    first[x]=tt;
}
int change()
{
    ll mn=1e15;
    int i,j,p;
    fo(i,st,en)
        if (pd[i]==ttt)
            for(p=first[i];p;p=next[p])
                if (c[p]&&pd[b[p]]!=ttt)
                    mn=min(mn,dis[b[p]]+cost[p]-dis[i]);
    if (mn==1e15) return 0;
    fo(i,st,en)
        if (pd[i]==ttt)
            dis[i]+=mn;
    return 1;
}
int flow(int x,int y)
{
    pd[x]=ttt;
    if (x==en)
    {
        ans-=dis[st]*y;
        return y;
    }
    for(int p=now[x];p;p=next[p])
        if (pd[b[p]]!=ttt&&c[p]&&dis[b[p]]+cost[p]==dis[x])
        {
            int l=flow(b[p],min(y,c[p]));
            if (l)
            {
                c[p]-=l;
                c[inv[p]]+=l;
                now[x]=p;
                return l;
            }
        }
    now[x]=0;
    return 0;
}

void clear()
{
    fo(i,1,n) first1[i]=0;
    fo(i,st,en) 
    {
        first[i]=0;
        dis[i]=0;
        pd[i]=0;
        du[i]=0;
    }
    ans=t1=ttt=tt=0;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d",&T);
    while (T--)
    {
        clear();
        scanf("%d %d",&n,&m);
        fo(i,1,n-1)
        {
            scanf("%d %d %d",&x,&y,&z);
            cr1(x,y,z);
            cr1(y,x,z);
        }
        dfs(1,0);
        fo(i,1,n-1)
        {
            x=(i-1)*2+1;
            if (dep[e1[x]]>dep[b1[x]]) x++;
            cr(e1[x],b1[x],1,0,c1[x]);
            cr(b1[x],e1[x],-1,0,0);
        }
        fo(i,1,m)
        {
            scanf("%d %d %d",&x,&y,&z);
            if (dep[x]>dep[y]) swap(x,y);
            ans+=z;
            du[y]--;
            du[x]++;
            cr(x,y,1,z,1);
            cr(y,x,-1,-z,0);
        }
        st=0;
        en=n+1;
        fo(i,1,n)
            if (du[i]>0)
            {
                cr(0,i,1,0,du[i]);
                cr(i,0,-1,0,0);
            }else
            {
                cr(i,en,1,0,-du[i]);
                cr(en,i,-1,0,0);
            }
        do
        {
            fo(i,st,en) now[i]=first[i];
            do ttt++;
            while (flow(st,N));
        }while (change());
        printf("%lld\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞赛)是中国的一项高水平计算机竞赛。为了帮助有志于参加NOI竞赛的青少年准备比赛,存在许多全面系统的NOI教程。 首先,对于初学者来说,NOI教程可以提供基础的编程知识。这包括了常见的编程语言(如C++、Java等)的语法和基础概念(如变量、循环、条件语句等)。这样的教程可以帮助初学者快速上手,并逐步提高他们的编程技巧。 其次,NOI教程还包含了一些常见的算法和数据结构。这些内容是NOI竞赛的关键,因为它们能够帮助选手更高效地解决问题。例如,教程可能涵盖排序算法、查找算法、图论算法等等。同时,也会介绍常见的数据结构如数组、链表、树等,以及它们的应用和实现方式。 此外,NOI教程还会涵盖一些高级的主题。这些主题可以帮助有一定编程基础的选手进一步提高他们的竞赛水平。这些主题可能包括动态规划、贪心算法、网络流等等。这些主题的学习可以帮助选手更深入地理解算法和问题的本质,从而更好地解决复杂的题目。 总的来说,NOI教程是一个超全面且系统的学习资源。它提供了从基础的编程知识到高级算法的内容,帮助选手全面提升他们的编程和竞赛能力。如果有志于参加NOI竞赛的青少年,通过学习NOI教程,他们能够更好地准备竞赛并取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值