Codeforces Round #513 E. Sergey and Subway 树形dp (树+dfs) 树上任意两点距离和

CF: 

dfs and similar    dp    trees    *2000

 

题意:

给定一个n个点的树,(n最大2e5),如果原图(树)中有边 u-v, v-w ,那么现在你可以连一条边u-w;

问任意两点间最短距离的和;

思路:

开始想的对原树dfs,用dp[i][2] 分别表示到i结点距离为偶数和奇数的最小花费,但是很麻烦。。。

其实:按照题目给定的要求,我们发现原来两点间距离L,L为偶数-现在花费是L/2,L为奇数-现在花费是(L+1)/2;

正男则反??:相当于在题目给定的基础上减少了一些花费,如上; 其实大小一样

然后我们还知道可以O(n) 的求取树上(任意两点间距离)的总和sum;我们只要知道有多少个点对(设num个)间距离是奇数就好了,然后ans = (sum+num) / 2; 

求距离为奇数的点对时,把树看作奇数层偶数层就好了,这些点对一定是奇数层和偶数层分别一个点;

ps 求树上任意两点间距离的和就是跑dfs的时候,用cnt[u]表示以u为根结点子树的大小(结点的个数),然后cnt[u] * (n-cnt[u])

相当于算边的贡献,这条边就是u跟父结点的连边;

 

 

#include

using namespace std;

#define out fflush(stdout);
#define fast ios::sync_with_stdio(0),cin.tie(0);
#define FI first
#define SE second

typedef long long ll;
typedef pair P;

const int maxn = 2e5 + 7;
const int INF = 0x3f3f3f3f;
const ll mod = 998244353;


ll n;
vector vec[maxn];
ll ans = 0, num = 0;
ll cnt[maxn];

void dfs(int id, int f, int ce) {
    if(ce) num++;
    cnt[id] = 1;
    for(auto i : vec[id]) {
        if(i == f) continue;
        dfs(i, id, ce^1);
        cnt[id] += cnt[i];
    }
    ans += (cnt[id] * (n-cnt[id]));
}

int main() {
    scanf("%lld", &n);
    int u, v;
    for(int i = 1; i < n; ++i) {
        scanf("%d%d", &u, &v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    dfs(1, -1, 1);
    ans = (ans + num*(n-num)) / 2LL;
    printf("%lld\n", ans);
    return 0;
}

 

你可能感兴趣的:(DFS,codefoces,DP,2018模板整理)