[GDOI模拟2015.12.26][USACO 2013OPEN]阴阳(yinyang)

11 篇文章 0 订阅
2 篇文章 0 订阅

题目大意

一棵包含 n 个节点的树,每条边都有类型0 1 ,求能满足一下条件的路径总数:
对于路径 [st,en] ,存在中间点 xst,en ,使得路径 [st,x] 上两种类型的边数总数相同,路径 [x,en] 上亦是如此。
1n100000


题目分析

两类边,要求总数相等,首先应该想到将边的两种反映在权值上,分别为 +1 , 1 。那么边数相同即和为 0 。满足条件的路径一定是两条权值为0路径接在一起。
这种路径求解问题的一般思路是点分治(你打边分治我不拦你),这题也不例外。
我们规定 dist(x,y) x ,y两点最短距离。
我们考虑当前重心点 x (注意,x不一定为中间点),如果存在一条穿过 x 的路径满足条件,设这条路径两个端点分别为st en 。那么一定满足 dist(x,st)=dist(x,en) 。我们可以考虑从这个条件下手。
为了避免重复,我们按子树顺序枚举路径的一个端点,从已处理的子树中找出能构成满足条件路径的点。
我们设 fird 为已处理的子树中满足: dist(x,y)=dfy,dist(x,f)d y 的个数。设secd为已处理的子树中满足: dist(x,y)=df使dist(x,f)=d y 的个数。
设当前枚举的结束点为e,显然,如果 e 存在祖先f使得 dist(x,f)=dist(x,e) ,那么该点对答案的贡献即为 firdist(x,e)+secdist(x,e) (已经存在中间点,不需要强制要求另一半路径存在中间点)。
如果 e 不存在祖先f使得 dist(x,f)=dist(x,e) ,那么该点对答案的贡献即为 secdist(x,e) (目前没有中间点,需要强制要求另一半路径存在中间点)。
当然具体细节由读者自己讨论,需要注意重心为中间点的情况。
时间复杂度 O(nlogn2)


代码实现

#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch))
    {
        if (ch=='-')
            f=-1;
        ch=getchar();
    }
    while (isdigit(ch))
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}

const int N=100000;
const int M=N-1;
const int E=M<<1;

int last[N+1],fa[N+1],dis[N+1],size[N+1];
int tov[E+1],next[E+1],len[E+1];
int fir[N*2+1],sec[N*2+1];
bool unable[N+1];
long long ans;
int tot,n;

void insert(int x,int y,int z)
{
    tov[++tot]=y;
    len[tot]=z;
    next[tot]=last[x];
    last[x]=tot;
}

int que[N+1],head,tail;

int search_core(int u)
{
    head=0,tail=1;
    que[1]=u;
    int x;
    while (head!=tail)
    {
        x=que[++head];
        size[x]=1;
        int i=last[x],y;
        while (i)
        {
            y=tov[i];
            if (y!=fa[x]&&!unable[y])
            {
                fa[y]=x;
                que[++tail]=y;
            }
            i=next[i];
        }
    }
    for (head=tail;head>1;head--)
        size[fa[que[head]]]+=size[que[head]];
    int core=0,csize=n+1;
    for (head=1;head<=tail;head++)
    {
        x=que[head];
        int i=last[x],y;
        int maxs=0;
        while (i)
        {
            y=tov[i];
            if (y!=fa[x]&&!unable[y])
                maxs=max(maxs,size[y]);
            i=next[i];
        }
        maxs=max(size[u]-size[x],maxs);
        if (maxs<csize)
        {
            csize=maxs;
            core=x;
        }
    }
    return core;
}

int extra[2][N*2+1];
bool exist[N*2+1];

int dfs(int x)
{
    int ret=1,i=last[x],y;
    extra[exist[dis[x]+n]][dis[x]+n]++;
    if (exist[dis[x]+n]||!exist[dis[x]+n]&&!dis[x])
        ans+=fir[-dis[x]+n]+sec[-dis[x]+n];
    else
        ans+=sec[-dis[x]+n];
    if (!dis[x]&&exist[n])
        ans++;
    bool rec=exist[dis[x]+n];
    exist[dis[x]+n]=true;
    while (i)
    {
        y=tov[i];
        if (!unable[y]&&y!=fa[x])
        {
            fa[y]=x;
            dis[y]=dis[x]+len[i];
            ret=max(ret,dfs(y)+1);
        }
        i=next[i];
    }
    exist[dis[x]+n]=rec;
    return ret;
}

void calc(int x)
{
    x=search_core(x);
    int i=last[x],y,maxs=0;
    while (i)
    {
        y=tov[i];
        if (!unable[y])
        {
            fa[y]=x;
            dis[y]=len[i];
            int deep=dfs(y);
            for (int j=n-deep;j<=n+deep;j++)
            {
                fir[j]+=extra[0][j];
                sec[j]+=extra[1][j];
                extra[0][j]=extra[1][j]=0;
                exist[j]=false;
            }
            maxs=max(maxs,deep);
        }
        i=next[i];
    }
    for (i=n-maxs;i<=n+maxs;i++)
        fir[i]=sec[i]=0;
    unable[x]=true;
    i=last[x];
    while (i)
    {
        y=tov[i];
        if (!unable[y])
            calc(y);
        i=next[i];
    }
}

int main()
{
    freopen("yinyang.in","r",stdin);
    freopen("yinyang.out","w",stdout);
    n=read();
    for (int i=1,x,y;i<n;i++)
    {
        x=read(),y=read();
        bool z=read();
        insert(x,y,z?1:-1);
        insert(y,x,z?1:-1);
    }
    calc(1);
    printf("%lld\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值