2024牛客多校第四场F.Good Tree 挑战全网最详解

好吧标题党了一回,但我相信有不少人被出题人的那句“手玩一下就知道了”无语住了

像我这种憨憨一旦想偏了就救不回来了,于是困惑了好久,在雨巨的指导下彻底搞懂

(此处大声谢谢雨巨,又有实力又会讲题又认真答疑每一个问题,呜呜呜我永远的姐)

题意简单来说就是定义f(i)为树上i点到其他所有点的距离之和,给定x,要求构造一棵树使得存在点u,v满足f(v)-f(v)=x,且所用的节点最少。

不妨把树上AB所在的路径拉出来,假设A子树里还有其他点,B子树里还有其他点

我们发现AB之间的点,因为有对称性,对A和B的贡献都是一样的

但是A子树中的点就不一定了,A到B一共要走4条边,A中有3个点,走到A的总贡献是3*1=3,走到B的总贡献是(1+4)*3=15

多了的12其实就是A子树中的点数*AB链上的边数=3*4=12

B子树同理,多了的部分是2*4=8

且在f(A)-f(B)中,A子树中的贡献是负值,也就是-12,B子树中的贡献是正值,也就是+8。

 

现在希望考虑极限情况,钦定f(A)的值能取到最大

 这时A的子树为空是最好的,因为它们只能贡献负数,那么就变成了:

 不妨设A到B的路径上共经过了P个点,则B的子树里有N-P-1个点,对f(A)-f(B)能产生的贡献一共是P*(N-P-1)

根据均值不等式,和一定时两数乘积要最大,必然要两数尽量相等,即P=N-P-1,P=(N-1)/2

当N-1=2k时,也就是N为奇数时,P值即为k

f(A)-f(B)取到极值K*K

当N-1=2k+1时,也就是N为偶数时,P值为k+1,B子树里有k个

 

f(A)-f(B)取到极值(K+1)*K

至此我们有结论一:

输入为K^2时,答案为2K+1,输入为K*(K+1)时,答案为2K+2,输入为(K+1)*(K+1)时,答案为2(K+1)+1=2K+3

而在这之间,由于答案具有单调性,且每个点我们都取到了最大值,[K*K+1,K*K+K-1]肯定是比2K+1大的

 现在到了最麻烦的部分,证明:

① 在输入为[K*K+K+1,(K+1)^2)时,证明2K+3能覆盖到所有值

先考虑取到K*K+K,只要在原来K*K的基础上在B的子树里再接一个点就能+K

接下来的事情就有趣了

首先怎么整+1?

发现如果A到B(不包含B)的个数为奇数的话,只要取中间且尽量靠B的点就能+1

因为奇数总是可以拆成k+1,k的和的形式,只要让A得到的贡献是k+1,必然比B多1

那怎么整+3?

发现在链上加点能+1,+3,+5...

这样就在原来2K+1的基础上再加两个点(一个在子树内,一个在路径上)得到了[K*K+K+1,(K+1)^2)里一半的值,下图是k=5时的例子

那怎么整+2?

发现如果最开始凑K的那个点一直留在B的子树内的话,怎么加都是1,3,5,没法得到2,4,6

这是因为在AB路径上每多走一条边,对A是+1,对B是-1,贡献的变化肯定是偶数

所以我们发挥人类智慧把这个点拉到链上,这也是让贡献+K

 

 

 

发现取中间加点贡献为0,往右边挪一个贡献+2,再往右一个+4...

这样就取到了另外一半的值。

当k=偶数时同理,至此我们证明了当输入在[K*K+K+1,(K+1)^2),2k+3能覆盖所有值。

②那么用2k+2能否取遍[K*K+1,K*K+K) ?

这次因为不需要先凑出K,我们先只考虑多出来的一个点放哪。

当K为奇数时,根据上面的推理,从中间往B加依次能得到+1,+3,...的贡献

当K为偶数时,依次能得到+2,+4,...的贡献

也就是说如果x-K*K(这里x是输入的)和K同余的话,一定能只用一个点凑到。

 如果不同余呢?

这时只加一个点无论如何是不够的,不管怎么调整(加在子树上无解,路上上无解,调整子树大小还会变小),一定至少需要2个点。

现在证明2个点一定能凑出来:

如果K是奇数:原本加一个点只能+1,+3,多了一个点后+2可以通过在+1的地方加两个点,用1+1凑,4可以用1和3凑,6可以用1和5凑...

如果K是偶数:原本只能+2,+1怎么凑?先把子树中的一个点拎到链上,答案就从K*K变成(K+1)*(K-1)=K*K-1

接下来手上有两个点了,能凑出1+1=2,1+3=4......加上K*K-1就能依次得到K*K+1,K*K+3......

至此我们证完了结论②。

整理一下结论:

附上队友写的代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
int T,x;
int sol(){
    cin >> x;
    int n=sqrt(x);
    while (n*n>x) n--; 
    //printf("n=%lld\n",n);
    if (n*n==x) return 2*n+1;
    if (n*n+n>=x){
        if ((n*n+n-x)%2) return 2*n+3;
        return 2*n+2;
    }
    else{
        return 2*n+3;
    }
}
void test(){
    for (int i=1;i<=10;i++){
        x=i;
        printf("%lld:%lld\n",i,sol());
    }
}
signed main(){
//    test();
    cin >> T;
    while(T--) cout << sol() << endl;
    return 0;
}

 

posted @ 2024-07-28 17:38  liyishui  阅读(36)  评论(0编辑  收藏  举报