zoukankan      html  css  js  c++  java
  • 【题解】P3565 [POI2014]HOT-Hotels

    P3565 [POI2014]HOT-Hotels

    声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。

    题目描述

    给定一棵树,在树上选 (3) 个点,要求两两距离相等,求方案数。

    (\)


    (\)

    (Solution)

    由于边权不为负(这道题里都为单位长),所以画画图可以发现三个点必定是以下两种形态( (1,3,4)(5,7,8)

    其实这是一种,只不过树的形态不一样,根节点也就不一样了

    简而言之,就是三个点中间一定有一个过渡点,证明很简单多画几个图就好了

    根据这个图首先我们可以很快想到设 (f[i][j]) 为第 (i) 个点为根的子树中到 (i) 距离为 (j) 的儿子个数

    然后呢?你会不会想到再设一个 (g[i][j]) 表示从 (i) 结点往上走 (j) 步可到达的点数?(不管你会不会反正我这样想了 (233)

    这样子设貌似是可行的,而且也很好计算答案,但是细想会发现一个问题——两个儿子的最短距离不一定经过了 (i)

    显然,儿子们之间的最短距离和 (LCA) 有~千丝万缕密不可分的~关系

    于是我们把 (g[i][j]) 状态的意义改变一下,变成 以 (i) 为根节点的子树中,有多少个二元组 ((x, y)) 满足 (x, y)(LCA(x, y)) 的距离都为 (d) ,且 (LCA(x, y))(i) 的距离为 (d - j) ,其中 (d) 可以取任意值

    用通俗的方式翻译一下就是,有多少对儿子,到他们的 (LCA) 的距离减去 (LCA)(i) 的距离为 (j)

    为什么要这样设呢?我认为有两点

    (1.) 可以方便地统计答案(后面再说

    (2.) 转移比较方便,对于每个儿子的子树,只用继承 (g[i][j] = sum g[son][j + 1]),对于不同的儿子则按照正常树形dp操作进行合并

    最后解决——统计答案!

    对于每个 (i) 分两种情况

    (1.)(g[i][j']) 中取两个点,(f[son][j]) 中取一个点

    则有 (d * 2 = d + d - j' + 1 + j) ,解得 (j' = j + 1)

    (ans += g[i][j + 1] * f[son][j])

    (2.)(f[i][j']) 中取一个点,(g[son][j]) 中取一个点

    则有 (d * 2 = d + d - j + 1 + j') ,解得 (j' = j - 1)

    (ans += f[i][j - 1] * g[son][j])

    完结撒花✿✿ヽ(°▽°)ノ✿

    (\)


    (\)

    (Code)

    这道题空间只有 (62.5MB) qwq,(5000*5000) 的数组开不下,我弄了一个类似手写栈~玄学~的东西,菊花图是可以卡掉(可见这道题数据过水233,不过还是顺利A啦

    #include<bits/stdc++.h>
    #define ll long long
    #define F(i, x, y) for(int i = x; i <= y; ++ i)
    using namespace std;
    int read();
    const int N = 5e3 + 5;
    const int M = 3e3 + 5;
    int n, u, v;
    int tot, top, gar[N], num[N];
    int head[N], cnt, ver[N << 1], nxt[N << 1];
    ll ans, f[M][M], g[M][M];
    void add(int x, int y){ver[++ cnt] = y, nxt[cnt] = head[x], head[x] = cnt;}
    int kk()//相当于给它一个固定的数组编号
    {
    	if(gar[top]) return gar[top --];
    	return ++ tot;
    }
    void dp(int x, int fa)
    {
    	num[x] = kk(), f[num[x]][0] = 1;
    	for(int i = head[x]; i; i = nxt[i])
    	{
    		int son = ver[i], maxh = 1;
    		if(son == fa) continue;
    		dp(son, x);
    		for(int j = 1; f[num[son]][j]; ++ j) maxh = j + 1;
                    //注意树形dp更新顺序
    		F(j, 0, maxh) ans += g[num[x]][j + 1] * f[num[son]][j] + f[num[x]][j] * g[num[son]][j + 1];
    		F(j, 1, maxh) g[num[x]][j] += f[num[x]][j] * f[num[son]][j - 1];
    		F(j, 0, maxh) g[num[x]][j] += g[num[son]][j + 1];	
    		F(j, 1, maxh) f[num[x]][j] += f[num[son]][j - 1];
    		memset(f[num[son]], 0, sizeof(f[num[son]]));
    		memset(g[num[son]], 0, sizeof(g[num[son]]));
    		gar[++ top] = num[son];//这个儿子的所有贡献计算完了,可以把所有数据丢弃,回收它的数组编号
    	}
    }
    int main()
    {
    	n = read();
    	F(i, 1, n - 1) u = read(), v = read(), add(u, v), add(v, u);
    	dp(1, 0), printf("%lld", ans);
    	return 0;
    }
    int read()
    {
    	int x = 0, f = 1;
    	char c = getchar();
    	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return x * f;
    }
    
  • 相关阅读:
    c++ 对象大小内存占用分析
    运维(SA)修仙 之路 II
    分享好文章-Ansible 进阶技巧
    java JWT 登录认证
    ftp:500 OOPS: chroot
    centos 磁盘清理
    redis分析命令
    linux查找内容
    vim使用
    查看进程端口
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/13060658.html
Copyright © 2011-2022 走看看