标签:假设 read line 树根 alc 特征 math 怎么 int
点这里看题目。
原来数据的奇怪结尾就可以拿来判断特征呀
太简单就不说了。
考虑完全二叉树怎么做。
这里需要注意一点,就是:\(n=262143=2^{18}-1\) ,也就是说,数据实际上就是一棵满二叉树。
由于满二叉树具有极强的对称性,我们不难想到这样解决:
首先,答案一定包含 \(\frac{n(n+1)}{2}-rt\),其中 \(rt\) 是树的根。
考虑根的左子树上深度为 \(d\) (根的深度为 \(0\) )的节点,我们可以任选一个,计算出拆掉它和它父亲之间的边后,父亲那颗子树的贡献\(t\)。那么答案就应该加上 \(2^{d-1}\times t\) 。右子树同理。
这样计算就是 \(O(n\log_2n)\) 的。
这个部分还可以推出通项公式。该方法的依据是:
1 . 每个点拆掉了它和它父亲之后,它就是自己那部分的重心。
2 . 拆掉左边的任何边,根的右儿子一定是重心;拆掉右边,左儿子一定是重心。
3 . 拆掉一个叶子和它父亲连的边,根一定是重心。
然后就可以利用上述依据 \(O(n)\) 计算答案了。
为了方便,以下设 \(siz_u\) 表示 \(u\) 的子树的大小。
首先我们需要非常重要的一点:
对于在一个大小为 \(n\) 的树上的点 \(u\) ,若 \(n-siz_u\le \lfloor\frac n 2\rfloor\) ,且它不是重心,那么重心一定在 \(u\) 的重子树内。
看起来很好理解吧?考虑简单地说明一下。
假如 \(u\) 有 \(k\) 个重儿子,那么每个重儿子的 \(siz\le \frac {siz_u} k\le \lfloor\frac n 2\rfloor\) 。那么 \(u\) 就是重心,与假设不符,因而 \(u\) 只有一个重儿子。
同理可以说明, \(u\) 的重儿子的大小 \(>\lfloor\frac n 2\rfloor\) 。也即是说,对于 \(u\) 的非重儿子 \(v\) ,一定有 \(siz_v<\lfloor\frac n 2\rfloor\Rightarrow n - siz_v>\lfloor\frac n 2\rfloor\) 。所以重心一定在 \(u\) 的重儿子里面。
看看我们得到了什么?
如果 \(u\) 满足 \(n - siz_u\le \lfloor \frac n 2\rfloor\) ,那么:
1 . \(u\) 只有一个重儿子。
2 . 要么 \(u\) 是重心,要么重心在 \(u\) 的重子树里。
3 . 如果 \(u\) 是满足性质的最深的点,那么 \(u\) 就是重心,且 \(u\) 的父亲可能是重心。
第三条可以结合第二条推出。
也就是说:第一、二条告诉了我们重心的明确方向,第三条告诉了我们重心的判断标准,并且它还存在单调性。
那我们可以干什么了?
倍!增!呀!
以上三条直接教会我们,只要我们能处理出 \(u\) 的朝向重儿子的倍增数组,我们就可以快速地从树根开始求重心。
这样我们就可以在知道倍增数组后 \(O(\log_2n)\) 地求出重心。那么我们只需要枚举每一条边,维护倍增数组并计算重心即可。由于边原本可能是无序的,因而我们需要进行 DFS ,并中途维护信息(本质上还是在 “ 换根 ” ),然后就可以倍增出重心了。
时间复杂度是 \(O(n\log_2n)\) 。
int p[MAXN], pos[MAXN];
int head[MAXN], deg[MAXN];
void DFS( const int u, const int fa )
{
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
{
if( p[pos[u] << 1] )
p[pos[v] = pos[u] << 1 | 1] = v;
else
p[pos[v] = pos[u] << 1] = v;
DFS( v, u );
}
}
int main()
{
//...
for( int i = 1 ; i <= N ; i ++ )
if( deg[i] == 2 )
{
p[pos[i] = 1] = i;
DFS( i, 0 );
break;
}
LL ans = 1ll * N * ( N + 1 ) / 2 - p[1];
ans += 1ll * N / 2 * ( p[2] + p[3] );
ans += 1ll * ( N / 2 + 1 ) * p[1];
//...
}
typedef long long LL;
const int MAXN = 3e5 + 5, MAXLOG = 20;
template<typename _T>
_T MAX( const _T a, const _T b )
{
return a > b ? a : b;
}
int jump[MAXN][MAXLOG];
int hs[MAXN], phs[MAXN], fath[MAXN], siz[MAXN];
LL ans;
void addEdge( const int from, const int to )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
void init( const int u )
{
for( int i = 1 ; i <= lg2 ; i ++ )
jump[u][i] = jump[jump[u][i - 1]][i - 1];
}
void DFS( const int u, const int fa )
{
fath[u] = fa;
siz[u] = 1, hs[u] = phs[u] = 0;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
{
DFS( v, u ), siz[u] += siz[v];
if( siz[hs[u]] < siz[v] ) phs[u] = hs[u], hs[u] = v;
else if( siz[phs[u]] < siz[v] ) phs[u] = v;
}
jump[u][0] = hs[u];
init( u );
}
bool chk( const int u, const int all )
{
return MAX( siz[hs[u]], all - siz[u] ) <= all >> 1;
}
void calc( const int u, const int fa )
{
int ths = hs[u], tsiz = siz[u], p;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
{
if( hs[u] == v ) hs[u] = phs[u];
if( siz[fa] > siz[hs[u]] ) hs[u] = fa;
siz[u] = N - siz[v];
jump[u][0] = hs[u];
init( u );
p = u;
for( int i = lg2 ; ~ i ; i -- )
if( jump[p][i] && siz[u] - siz[jump[p][i]] <= siz[u] >> 1 )
p = jump[p][i];
ans += chk( p, siz[u] ) * p + chk( fath[p], siz[u] ) * fath[p];
p = v;
for( int i = lg2 ; ~ i ; i -- )
if( jump[p][i] && siz[v] - siz[jump[p][i]] <= siz[v] >> 1 )
p = jump[p][i];
ans += chk( p, siz[v] ) * p + chk( fath[p], siz[v] ) * fath[p];
fath[u] = v;
calc( v, u );
jump[u][0] = hs[u] = ths, siz[u] = tsiz, fath[u] = fa;
init( u );
}
}
void init()
{
cnt = ans = 0;
for( int i = 1 ; i <= N ; i ++ )
head[i] = 0;
}
int main()
{
//...
lg2 = log2( N ), init();
for( int i = 1, a, b ; i < N ; i ++ )
read( a ), read( b ), addEdge( a, b ), addEdge( b, a );
DFS( 1, 0 );
calc( 1, 0 );
//...
}
标签:假设 read line 树根 alc 特征 math 怎么 int
原文地址:https://www.cnblogs.com/crashed/p/13374135.html