BZOJ 3648: 寝室管理 树的点分治+乱搞

Description

T64有一个好朋友,叫T128。T128是寄宿生,并且最近被老师叫过去当宿管了。宿管可不是一件很好做的工作,碰巧T128有一个工作上的问题想请T64帮忙解决。

T128的寝室条件不是很好,所以没有很多钱来装修。礼间寝室仅由n-1条双向道路连接,而且任意两间寝室之间都可以互达。最近,T128被要求对一条路径上的所有寝室进行管理,这条路径不会重复经过某个点或某条边。但他不记得是哪条路径了。他只记得这条路径上有不少于k个寝室。于是,他想请T64帮忙数一下,有多少条这样的路径满足条件。
嗯…还有一个问题。由于最近有一些熊孩子不准晚上讲话很不爽,他们决定修筑一条“情报通道”,如果通道建成,寝室就变成了一个N个点N条边的无向图。并且,经过“情报通道”的路径也是合法的。T128心想:通道建成之前,T64还有一个高效的算法帮我数路径条数,但是通道建成之后,他还有办法吗?对,T64手忙脚乱,根本数不清有多少条路径。于是他找到了你。

Input

第一行为三个正整数N,M,K(2 ≤ K ≤ N),代表有n间寝室,m条边连接它们n-1 ≤ m ≤ N;m= n-1意味着“情报遁道”未被修好;m=n意味着“情报通道”已被修好),以及题目描述中的K。
接下来m行,每行两个正整数z,y,代表第x间寝室与第y间寝室之间有一条双向边。

Output

仅包含一个整数,代表经过至少K间寝室的路径条数。

Sample Input

5 5

1 3

2 4

3 5

4 1

5 2

Sample Output

20

题解

如果说题目的限制是n个点n-1条边,那么这是一道傻逼点分治,但是n个点n条边怎么办呢,首先这是一个基环外向树,我们先断掉环上的一条边,然后点分治出答案,这样不过这条边的答案我们就算出来了,然后我们再瞎搞一搞就能算出经过这条边的方案数。(码一码也是挺爽的233.

#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<iomanip>
using namespace std;
struct bian
{
    int l,r;
    bool ban;
}a[300000];
int fir[300000];
int nex[300000];
int du[300000];
int tot=1;
void add_edge(int l,int r)
{
    a[++tot].l=l;
    a[tot].r=r;
    a[tot].ban=false;
    nex[tot]=fir[l];
    fir[l]=tot;
    du[r]++;
}
int siz[300000];
int fa[300000];
int center;
void get_tree_center(int u,int fro,int zong)
{
    fa[u]=fro;
    siz[u]=1;
    bool pd=true;
    for(int o=fir[u];o;o=nex[o])
    {
        if(a[o].ban || a[o].r==fro) continue;
        get_tree_center(a[o].r,u,zong);
        siz[u]+=siz[a[o].r];
        if(siz[a[o].r]>zong/2) pd=false;
    }
    if(zong-siz[u]>zong/2) pd=false;
    if(pd) center=u;
}
int c[300000];
int tim[300000];
int n,m,k;
long long ans=0;
int T;
void add_val(int t,int val)
{
    t++;
    while(t)
    {
        if(tim[t]!=T) c[t]=0;
        tim[t]=T;
        c[t]+=val;
        t-=(t&(-t));
    }
}
long long get_val(int t)
{
    t++;
    long long re=0;
    while(t<=n+1)
    {
        if(tim[t]!=T) c[t]=0;
        tim[t]=T;
        re+=c[t];
        t+=(t&(-t));
    }
    return re;
}
void dfs1(int u,int fro,int v)
{
    ans+=get_val(max(0,k-v));
    for(int o=fir[u];o;o=nex[o])
    {
        if(a[o].r==fro || a[o].ban) continue;
        dfs1(a[o].r,u,v+1);
    }
}
void dfs2(int u,int fro,int v,int val)
{
    add_val(v,val);
    for(int o=fir[u];o;o=nex[o])
    {
        if(a[o].r==fro || a[o].ban) continue;
        dfs2(a[o].r,u,v+1,val);
    }
}
void tree_divide(int u)
{
    int zong=siz[u];
    get_tree_center(u,0,siz[u]);
    u=center;
    if(fa[u]) siz[fa[u]]=zong-siz[u];
    T++;
    add_val(0,1);
    ans+=get_val(k);
    for(int o=fir[u];o;o=nex[o])
    {
        if(a[o].ban) continue;
        dfs1(a[o].r,u,1);
        dfs2(a[o].r,u,1,1);
    }
    for(int o=fir[u];o;o=nex[o])
    {
        if(a[o].ban) continue;
        a[o].ban=true;
        a[o^1].ban=true;
        tree_divide(a[o].r);
    }
}
void toposort()
{
    static int dui[300000];
    int s=1,t=1;
    for(int i=1;i<=n;i++)
        if(du[i]==1)
            dui[t++]=i;
    while(s<t)
    {
        int u=dui[s];
        s++;
        for(int o=fir[u];o;o=nex[o])
        {
            du[a[o].r]--;
            if(du[a[o].r]==1) dui[t++]=a[o].r;
        }
    }
}
int h[300000];
int top=0;
bool vis[300000];
int jilu=-1;
void find_huan(int u)
{
    vis[u]=true;
    h[++top]=u;
    for(int o=fir[u];o;o=nex[o])
    {
        if(vis[a[o].r]) continue;
        if(du[a[o].r]>1)
        {
            if(jilu==-1) jilu=o;
            find_huan(a[o].r);
        }
    }
}
void solve()
{
    toposort();
    for(int i=1;i<=n;i++)
    {
        if(du[i]>1)
        {
            find_huan(i);
            break;
        }
    }
    a[jilu].ban=true;
    a[jilu^1].ban=true;
    siz[1]=n;
    tree_divide(1);
    T++;
    for(int i=2;i<=tot;i++) a[i].ban=false;
    for(int i=2;i<=top;i++)
    {
        add_val(i-1,1);
        int u=h[i];
        for(int o=fir[u];o;o=nex[o])
        {
            if(du[a[o].r]>1) continue;
            dfs2(a[o].r,u,i,1);
        }
    }
    h[top+1]=h[1];
    int t=0;
    for(int i=top+1;i>=3;i--)
    {
        if(i!=top+1)
        {
            add_val(i-1,-1);
            int u=h[i];
            for(int o=fir[u];o;o=nex[o])
            {
                if(du[a[o].r]>1) continue;
                dfs2(a[o].r,u,i,-1);
            }
        }
        int u=h[i];
        ans+=get_val(max(0,k-t));
        for(int o=fir[u];o;o=nex[o])
        {
            if(du[a[o].r]>1) continue;
            dfs1(a[o].r,u,t+1);
        }
        t++;
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    k--;
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        add_edge(l,r);
        add_edge(r,l);
    }
    if(m==n-1)
    {
        siz[1]=n;
        tree_divide(1);
        cout<<ans<<endl;
        return 0;
    }
    solve();
    cout<<ans;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值