『寝室管理 基环树点分』

<更新提示>

<第一次更新>


<正文>

寝室管理

Description

T64有一个好朋友,叫T128。T128是寄宿生,并且最近被老师叫过去当宿管了。

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

T128心想:通道建成之前,T64还有一个高效的算法帮我数路径条数,但是通道建成之后,他还有办法吗? 对,T64手忙脚乱,根本数不清有多少条路径。于是他找到了你。

Input Format

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

接下来m行,每行两个正整数z,y,代表第x间寝室与第y间寝室之间有一条双向边。

Output Format

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

Sample Input

5 5 2
1 3
2 4
3 5
4 1
5 2

Sample Output

20

解析

如果不考虑那条情报通道,那就是一个点分治模板题,没什么难度,并且可以拿到\(50\)分的高分。

现在变成基环树上的路径统计,好像不能直接点分治了。那就考虑一下基环树的套路,一般来说,基环树问题可以有两种方法化简:\(1.\) 强制拆一条边,按照树的方式计算,然后计算强制加入这条边的贡献。\(2.\) 每次枚举一条环上的边拆掉,然后分别计算贡献,最后合并答案。

对于这道题,其实两种方法都可以使用,不过显然使用第一种会比较简单。

我们可以先找到基环树的环,然后把环上的一条边断开,对整棵树进行点分治,那么没有统计的贡献就只剩下了包含这条边的路径。

考虑如何统计这些路径,可以枚举环上的点,和点分治计算贡献一样用树状数组把半条路径的贡献存起来,然后找另外半条路径的时候计算贡献即可,使这两边的路径中间恰好有我们刚刚删去的那条边,这样就强制把这条边选进去了。

细节有点多,码量有点大,多看看是不是有点算重复了。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 100020 , INF = 0x3f3f3f3f;
inline int read(void)
{
	int x = 0 , w = 0; char ch = ' ';
	while ( !isdigit(ch) ) w |= ch == '-' , ch = getchar();
	while ( isdigit(ch) ) x = x * 10 + ch - 48 , ch = getchar();
	return w ? -x : x;
}
struct edge { int ver,next; } e[2*N];
int n,m,k,t,Head[N],fa[N],vis[N],loop[N],cnt,num;
int Max[N],size[N],dis[N],flag[N],root,tot,ban;
long long ans;
struct BinaryIndexedTree
{
    int c[N];
    inline int lowbit(int x) { return x & (-x); }
    inline void insert(int p,int v) { for (;p<=n;p+=lowbit(p)) c[p] += v; }
    inline int query(int p)
    {
        if ( p <= 0 ) return 0;
        int res = 0;
        for (;p;p-=lowbit(p)) res += c[p];
        return res;
    }
} Tree;
inline void insert(int x,int y)
{
    e[++t] = (edge){y,Head[x]} , Head[x] = t;
    e[++t] = (edge){x,Head[y]} , Head[y] = t;
}
inline void input(void)
{
    t = 1;
    n = read() , m = read() , k = read();
    for (int i=1;i<=m;i++)
        insert( read() , read() );
}
inline void dp(int x,int f)
{
    size[x] = 1 , Max[x] = 0;
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        dp( y , x );
        size[x] += size[y];
        Max[x] = max( Max[x] , size[y] );
    }
    Max[x] = max( Max[x] , tot - size[x] );
    if ( Max[x] < Max[root] ) root = x;
}
inline void dfs(int x,int f)
{
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        dis[y] = dis[x] + 1;
        dfs( y , x );
    }
}
inline void calc(int x,int f)
{
    ans += Tree.query( n ) - Tree.query( k - dis[x] );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        calc( y , x );
    }
}
inline void update(int x,int f,int v)
{
    Tree.insert( dis[x] , v );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        update( y , x , v );
    }
}
inline void divide(int x)
{
    flag[x] = true , dis[x] = 1;
    dfs( x , 0 );
    Tree.insert( 1 , 1 );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        calc( y , x ) , update( y , x , 1 );
    }
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        update( y , x , -1 );
    }
    Tree.insert( 1 , -1 );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( flag[y] ) continue;
        if ( i == ban || (i^1) == ban ) continue;
        tot = size[y] , root = 0;
        dp( y , 0 );
        divide( root );
    }
}
inline void findloop(int x)
{
    vis[x] = ++num;
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == fa[x] ) continue;
        if ( vis[y] )
        {
            if ( vis[y] < vis[x] ) continue;
            loop[++cnt] = y;
            for (;y!=x;y=fa[y]) loop[++cnt] = fa[y];
        }
        else fa[y] = x , findloop( y );
    }
}
inline void update_(int x,int f,int op,int v)
{
    Tree.insert( dis[x] + v , op );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        update_( y , x , op , v );
    }
}
inline void calc_(int x,int f,int v)
{
    ans += Tree.query( n ) - Tree.query( k - dis[x] - v - 1 );
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        if ( y == f || flag[y] ) continue;
        calc_( y , x , v );
    }
}
inline void solve(void)
{
    for (int i=Head[loop[1]];i;i=e[i].next)
        if ( e[i].ver == loop[cnt] )
            { ban = i; break; }
    root = 0 , tot = n , Max[0] = INF;
    dp( 1 , 0 ) , divide( root );
    ban = 0;
    memset( flag , 0 , sizeof flag );
    for (int i=1;i<=cnt;i++)
        flag[loop[i]] = true;
    for (int i=1;i<=cnt;i++)
    {
        dis[loop[i]] = 1;
        dfs( loop[i] , 0 );
    }
    for (int i=1;i<=cnt;i++)
        update_( loop[i] , 0 , 1 , i );
    for (int i=cnt;i>=1;i--)
        update_( loop[i] , 0 , -1 , i ),
        calc_( loop[i] , 0 , cnt - i - 1 );
}
int main(void)
{
    input();
    if ( m == n-1 )
    {
        root = 0 , tot = n , Max[0] = INF;
        dp( 1 , 0 );
        divide( root );
    }
    else findloop(1) , solve();
    printf("%lld\n",ans);
    return 0;
}


<后记>

posted @ 2019-08-24 12:43  Parsnip  阅读(188)  评论(0编辑  收藏  举报