【USACO 2013 open】【JZOJ 3234】阴阳

Description

Farmer John 正在在计划自己的农场漫步。他的农场的结构就像一棵树:农场有N个谷仓(1<= N <=100,000),分别由N-1条路链接。这样,他便可以通过这些谷仓间的道路遍及各个谷仓。Farmer John想要选择一条路线:这条路线的起点和终点分别为农场中两个不同的谷仓,这条路线不能重复经过一条边两次。Farmer John担心这条路径可能会偏长,所以他想在路线上寻找一个休息点(当然这个休息点不能为起点或者终点)。

每条边的两旁都是牛群,要么是Charcolais(白毛),要么是Angus(黑毛)。Farmer John是一个聪明人,所以他想要在他通过小路的同时平衡小路两侧阴阳的力量。他要选择一条路径使得他从起点到休息站,和从休息站到终点这两段路上都满足路两边的Charcolais牛群和Angus牛群总数量相同。

Farmer John好奇他能找到多少条如上所述的平衡的路径。我们认为,当且仅当两条路线的边的集合不同时,这两条路径才被认为是不同的,否则认为是相同的路线。就算路线上有多个有效的“休息站”的位置能使路线平衡,我们也只记为一条路线。

请帮助计算有多少条不同的平衡路线。

Solution

很显然这是一道点分治,
白毛为1,黑毛为-1,
我们每轮的点分治表示过重心的合法路径,(以下的距离指与根节点的路径和)
Cs 表示之前从根出发第一次遇到距离为s的点的个数, cs 为之前从根出发非第一次(也就是存在父亲(包括根)的和也为s)遇到距离为s的点的个数,当前的点为q,距离为s,
q对答案的贡献至少为 cs ,如果当前点存在父亲距离也等于s,那么还要加上 Cs ,因为我们必须找一个点作为休息点,
每轮更新一下数组即可
复杂的: O(2nlog(n))

Code

#include<iostream>
#include<cstdlib>
#include<cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long LL;
const int N=100500,maxlongint=2147483640;
int read(int &n)
{
    n=0;int q=0,w=1;char ch=getchar();
    for(;(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';q=q*10+ch-48,ch=getchar());
    n=q*w;return n;
}
int B[2*N][3],A[N],B0;
int n,hw[N],c[2*N],c1[2*N];
LL ans;
int alln,ce,ces,z[2*N];
bool bj[N];
void join(int q,int w,int e)
{
    B[++B0][0]=A[q],A[q]=B0,B[B0][1]=w,B[B0][2]=e;
    B[++B0][0]=A[w],A[w]=B0,B[B0][1]=q,B[B0][2]=e;
}
int dfs(int q,int fa)
{
    int w;hw[q]=1;
    for(int i=A[q];i;i=B[i][0])if(!bj[w=B[i][1]]&&fa!=w)hw[q]+=dfs(w,q);
    return hw[q];
}
void findc(int q,int fa)
{
    int s=alln-hw[q],w;
    for(int i=A[q];i;i=B[i][0])if(!bj[w=B[i][1]]&&fa!=w)findc(w,q),s=max(s,hw[B[i][1]]+1);
    if(ces>s)ces=s,ce=q;
}
void add(int q,int s0,int fa)
{
    c[s0+N]+=(z[s0+N]!=0);c1[s0+N]+=(z[s0+N]==0);z[s0+N]++;
    for(int i=A[q];i;i=B[i][0])if(!bj[B[i][1]]&&fa!=B[i][1])add(B[i][1],s0+B[i][2],q);
    z[s0+N]--;
}
void find(int q,int s0,int fa)
{
    ans+=c[N-s0]+(z[s0+N]!=0)*c1[N-s0];
    z[s0+N]++;
    for(int i=A[q];i;i=B[i][0])if(!bj[B[i][1]]&&fa!=B[i][1])find(B[i][1],s0+B[i][2],q);
    z[s0+N]--;
}
void divide(int q)
{
    alln=dfs(q,q);
    ces=maxlongint;
    findc(q,q);bj[q=ce]=1;
    fill(c+N-alln-1,c+N+alln+1,0);fill(c1+N-alln-1,c1+N+alln+1,0);c1[N]=1;
    for(int i=A[q];i;i=B[i][0])if(!bj[B[i][1]])
    {
        z[N]=0;find(B[i][1],B[i][2],q);
        z[N]=1;add(B[i][1],B[i][2],q);
    }
    for(int i=A[q];i;i=B[i][0])if(!bj[B[i][1]])divide(B[i][1]);
}
int main()
{
    int q,w,e;
    read(n);
    fo(i,1,n-1)read(q),read(w),read(e),join(q,w,(e?1:-1));
    ans=0;
    divide(1);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值