【JZOJ 5262】 树

本文介绍了一种针对树形结构的路径匹配算法,通过分析树的特性,特别是叶子节点的特点,提出了一种高效的O(n)算法解决方案。该算法能够确定每个节点作为路径起点或终点的次数,并确保匹配顺序不会影响最终结果。

Description

这里写图片描述
n<=10^6,存在m使得m<=n

Analysis

这种题看起来很难做,但是我们其实要抓住突破口——叶子结点
考虑树上每条边走的次数,正负来表示方向,那么我们可以从叶子结点一路递推到所有点
也就是,树上每条边走的次数是个定值
那么也可以顺便求出每个点作为路径起点/终点的次数
且顺带得出了一个结论:每个点要么只作为起点,要么只作为终点
这样是不是按字典序来配对就可以了呢?
很幸运,这题是的,匹配顺序不会对点权造成影响,只需要发现路径A->B,C->D与路径A->D,C->B是等价的
O(n)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v) for(int i=last[v],u=to[i];i;i=next[i],u=to[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
void read(int &n)
{
    n=0;char ch;int p=1;
    for(ch=getchar();ch<'0' || ch>'9';ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';
    n*=p;
}
const int N=1e6+5;
int n,tot,to[N*2],next[N*2],last[N],a[N];
ll f[N],g[N];
void link(int u,int v){to[++tot]=v,next[tot]=last[u],last[u]=tot;}
void dfs(int v,int fr)
{
    efo(i,v)
        if(u!=fr)
        {
            dfs(u,v);
            f[u]=(u<v)?a[u]:-a[u];
            g[u]+=f[u],g[v]-=f[u];
            a[v]-=a[u];a[u]=0;
        }
}
int main()
{
    int u,v;
    read(n);
    fo(i,1,n) read(a[i]);
    fo(i,1,n-1)
    {
        read(u);read(v);
        link(u,v),link(v,u);
    }
    dfs(1,1);
    int ans=0;
    fo(i,1,n) ans+=abs(g[i]);
    printf("%d\n",ans/2);
    if(ans/2==0) return 0;
    for(int i=1,j=1;i<=n && j<=n;)
    {
        while(i<n && g[i]<=0) i++;
        while(j<n && g[j]>=0) j++;
        if(!g[i] || !g[j]) break;
        printf("%d %d\n",i,j);
        if(!--g[i]) i++;
        if(!++g[j]) j++;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值