寝室管理
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;
}
『寝室管理 基环树点分』
原文地址:https://www.cnblogs.com/Parsnip/p/11404248.html