2019CCPC-江西省赛 Cotree(HDU-6567)

博客地址:https://startcraft.cn

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6567

题目大意

给你两棵树,让你在两棵树之间加一条边,使得两棵树联通且任意两结点之间的距离之和最短
就是最小化这个式子
i=1nj=i+1ndis(i,j) \sum_{i=1}^n\sum_{j=i+1}^ndis\left(i,j\right)

思路

首先分别对两棵树TreeATree_ATreeBTree_B,我们在TreeATree_A中找一个点SAS_A,使得TreeATree_A中所有点到SAS_A的距离之和最小,同理在TreeBTree_B中找到SBS_B,将这两个点连接起来,就是符合题目要求的边.
因为两棵树之间只有这一条边相连,那么TreeATree_A中的点要去TreeBTree_B,就必须经过SA,SBS_A,S_B,又因为SA,SBS_A,S_B是各自的树里面的结点距离之和最小的,那么把它们连起来就是符合题意的
(其实SA,SBS_A,S_B就是树的重心)

那么怎样找SAS_A呢,首先以任意一个顶点为根,假设以11为根,维护两个数组sum[i]sum[i],表示ii的子节点到ii的的距离之和,sum_num[i]sum\_num[i]表示ii的子节点的个数,现在以11为根做一遍DFS可以得到这两个数组。
然后开始换根,假设现在的根是nownow, vvnownow的子节点,那么当根从nownow变到vv

  • sum_num[now]=(sum_num[v]+1)sum\_num[now]-=(sum\_num[v]+1)vvvv的子节点不再是nownow的子节点,遂减去
  • sum[now]=(sum[v]+sum_num[v]+1)sum[now]-=(sum[v]+sum\_num[v]+1)vvvv的子节点不再走到nownow,这部分的权值是它们走到vv的权值加上它们再走一步走到nownow,再走一步走到nownow的值就是结点个数1*1,即(sum[v]+sum_num[v]+1)(sum[v]+sum\_num[v]+1)
  • sum_num[v]+=sum_num[now]+1sum\_num[v]+=sum\_num[now]+1nownownownow的子节点变成了vv的子节点,注意:这里的sum_num[now]sum\_num[now]已经是经过上面变换之后的
  • sum[v]+=(sum[now]+sum_num[now]+1)sum[v]+=(sum[now]+sum\_num[now]+1)nownownownow的子节点将会走到vv,这部分的权值是它们走到nownow的权值加上它们再走一步走到vv,再走一步走到vv的值就是结点个数1*1,即(sum[now]+sum_num[now]+1)(sum[now]+sum\_num[now]+1),注意:这里的关于nownow的数组已经是经过前两步变换过的

然后遍历树中的所有结点我们就能得到以SAS_A为根,最小的sum[SA]sum[S_A]
找出SA,SBS_A,S_B之后就可以计算题目中的那个式子了:
TreeATree_A中的每一个点都要与TreeBTree_B中的每一个点计算距离,那么首先TreeATree_A中的每一个点要先走到SBS_B,距离就是sum[SA]+TreeAsum[S_A]+Tree_A中结点个数,这个式子的含义就是TreeATree_A中所有点先走到SAS_A,再走一步到SBS_B
又因为TreeBTree_B中有NN个结点,所以TreeATree_A中的每一个点都要走NN次去SBS_B这条路,所以要乘以NN
然后SBS_BTreeBTree_B中所有点的距离之和是sum[SB]sum[S_B],这个值会被计算TreeATree_A中结点个数次,所以要乘以TreeATree_A中的结点个数

最后的结果就是
(sum[SA]+sum_num[SA]+1)(sum_num[SB]+1)+sum[SB](sum_num[SA]+1) (sum[S_A]+sum\_num[S_A]+1)*(sum\_num[S_B]+1)+sum[S_B]*(sum\_num[S_A]+1)
其中sum_num[SA]+1sum\_num[S_A]+1sum_num[SB]+1sum\_num[S_B]+1就是TreeATree_ATreeBTree_B中的结点个数.
上面是两棵树之间点的距离之和,最后再加上两棵树各自内部的点之间的距离之和就行了,这个可以再换根的时候就得到换根时每一个点都作为根计算过sum[i]sum[i],只需要将每个sum[i]sum[i]累加起来最后除以2就行了

AC代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
#define wfor(i,j,k) for(i=j;i<k;++i)
#define mfor(i,j,k) for(i=j;i>=k;--i)
// void read(ll &x) {
//  char ch = getchar(); x = 0;
//  for (; ch < '0' || ch > '9'; ch = getchar());
//  for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
// }
const ll maxn = 100005;
struct EDGE
{
    ll next;
    ll end;
};
ll head[maxn];
EDGE edge[maxn * 2];
ll cnt;
void add(ll beg, ll end)
{
    edge[++cnt].next = head[beg];
    edge[cnt].end = end;
    head[beg] = cnt;
}
ll vis[maxn];
ll sum[maxn];
ll sum_num[maxn];
void dfs(ll now)//dfs先得到以任意一个结点为根的sum和sum_num数组的值
{
    ll i;
    ll temp_sum = 0;
    ll temp_sum_num = 0;
    for (i = head[now]; i; i = edge[i].next)
    {
        ll v = edge[i].end;
        if (!vis[v])
        {
            vis[v] = 1;
            dfs(v);
            temp_sum += sum_num[v] + sum[v] + 1;
            temp_sum_num += sum_num[v] + 1;
        }
    }
    sum_num[now] = temp_sum_num;
    sum[now] = temp_sum;
}
ll root_1;
ll sum_root1;
ll root_2;
ll sum_root2;
ll find_root(ll now, ll &root, ll &sum_root)//换根
{
    ll i;
    ll tot = 0;
    tot += sum[now];//累加以每一个点为根的sum[i]
    ll temp1 = sum[now];
    ll temp2 = sum_num[now];
    for (i = head[now]; i; i = edge[i].next)
    {
        ll v = edge[i].end;
        if (!vis[v])
        {
            vis[v] = 1;
	    /**四步变换**/
            sum[now] -= (sum[v] + sum_num[v] + 1);
            sum_num[now] -= (sum_num[v] + 1);
            sum[v] += sum[now] + sum_num[now] + 1;
            sum_num[v] += sum_num[now] + 1;
            if (sum[v] < sum_root)//找sum的最小值
            {
                sum_root = sum[v];
                root = v;
            }
            tot += find_root(v, root, sum_root);
            sum[now] = temp1;
            sum_num[now] = temp2;
        }
    }
    return tot;
}
int main()
{
    std::ios::sync_with_stdio(false);
#ifdef test
    freopen("F:\\Desktop\\question\\in.txt", "r", stdin);
#endif
#ifdef ubuntu
    freopen("/home/time/debug/debug/in", "r", stdin);
    freopen("/home/time/debug/debug/out", "w", stdout);
#endif
    ll n;
    cin >> n;
    ll i;
    wfor(i, 0, n - 2)
    {
        ll u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    vis[1] = 1;
    dfs(1);
    root_1 = 1;
    sum_root1 = sum[root_1];
    wfor(i, 1, n + 1)
    {
        if (!vis[i])
        {
            vis[i] = 1;
            root_2 = i;
            dfs(i);
            break;
        }
    }
    sum_root2 = sum[root_2];
    memset(vis, 0, sizeof(vis));
    ll ans = 0;
    vis[root_1] = 1;
    ll temp = find_root(root_1, root_1, sum_root1);
    ans += temp / 2;
    vis[root_2] = 1;
    temp = find_root(root_2, root_2, sum_root2);
    ans += temp / 2;
    /**两棵树之间的距离**/
    ans += (sum_root2) * (sum_num[root_1] + 1) + (sum_root1 + sum_num[root_1] + 1) * (sum_num[root_2] + 1);
    cout << ans << endl;
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章