【ybt高效进阶 21165 / 150C】【nowcoder 1103B】树上交集 / 路径计数机(换根DP)(树形DP)

123 篇文章 2 订阅
89 篇文章 0 订阅
本文详细介绍了如何解决一道关于树上路径计数并寻找四元组(a,b,c,d)的题目,其中a到b和c到d的路径长度分别为p和q且不相交。通过动态规划和换根操作,计算了路径端点在子树内外的路径数量,最终得出所有可能的四元组组合。代码中展示了具体的实现过程。
摘要由CSDN通过智能技术生成

树上交集 / 路径计数机

题目链接:ybt高效进阶 21165 / 150C / nowcoder 1103B

题目大意

给你一棵树,问你能找到多少个四元组 (a,b,c,d),满足 a 到 b 边数为 p,c 到 d 边数为 q,而且两条路径没有交。

思路

考虑求不交比较难,我们搞有交的。

那不难想出两条路径就两种情况,一个是公用同一个 LCA,要么是有一条路径穿过了另一条路径的 LCA。
然后我们就以 LCA 为中心去搞,考虑求出这四个东西: f p i , f q i , g p i , g q i fp_i,fq_i,gp_i,gq_i fpi,fqi,gpi,gqi,分别表示从 i i i 出发,经过 p / q p/q p/q 条边,然后到的是 i i i 子树内 / 外的点。

那不难想到所有的四元组个数就是: ∑ i = 1 n f p i ∑ i = 1 n f q i \sum\limits_{i=1}^n fp_i\sum\limits_{i=1}^n fq_i i=1nfpii=1nfqi
然后有交的: ∑ i = 1 n ( f p i f q i + f p i g q i + g p i f q i ) \sum\limits_{i=1}^n(fp_ifq_i+fp_igq_i+gp_ifq_i) i=1n(fpifqi+fpigqi+gpifqi)
(后面两个都是第二个情况,只是谁穿过不同而已)

那接下来就是要求这四个数组。
那不难想出可以先搞路径一段是在 i i i 上的,然后把两个路径拼上得到上面的数组。
(两条路径的长度之和已经确定,就直接枚举一条的长度)

然后设 f i , j , g i , j f_{i,j},g_{i,j} fi,j,gi,j 为有多少条路径一端是 i i i,另一端在 i i i 子树内 / 外,然后路径长度是 j j j 的路径数。
f i , j f_{i,j} fi,j 可以直接 DP 下去, f i , j = ∑ k = s o n i f k , j − 1 , f i , 0 = 1 f_{i,j}=\sum_{k=son_i} f_{k,j-1},f_{i,0}=1 fi,j=k=sonifk,j1,fi,0=1

至于 g i , j g_{i,j} gi,j,我们想,如果我们求出以 i i i 为根的时候的 f i , j f_{i,j} fi,j,那这个就是不管子树内外的,减去在子树内的(一开始算出的 f i , j f_{i,j} fi,j),就是在子树外的了。
那我们考虑换根一下就好了。(记得跑完换回来)

然后就好啦。

代码

#include<map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	int to, nxt;
}e[6001];
int n, p, q, x, y;
int le[3001], KK;
ll ans, f[3001][3001], g[3001][3001];//这个是一个端点是 i,另一个端点在 i 子树内 / 外,路径长度为 j 的个数
ll fp[3001], fq[3001], gp[3001], gq[3001];//我们要求的东西
ll nw[3001][3001];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK; 
}

void dfs_f(int now, int father) {
	f[now][0] = 1;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs_f(e[i].to, now);
			for (int j = 1; j <= p; j++) {//把两个从 now 出发的拼起来
				fp[now] += (j == 0 ? 1 : f[e[i].to][j - 1]) * f[now][p - j];
			}
			for (int j = 1; j <= q; j++) {
				fq[now] += (j == 0 ? 1 : f[e[i].to][j - 1]) * f[now][q - j];
			}
			for (int j = 1; j < n; j++)
				f[now][j] += f[e[i].to][j - 1];
		}
}

void dfs_g(int now, int father) {
	for (int i = 0; i < n; i++)
		g[now][i] = nw[now][i] - f[now][i];
	g[now][0] = 1;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			for (int j = 1; j < n; j++)//换根
				nw[now][j] -= nw[e[i].to][j - 1];
			for (int j = 1; j < n; j++)
				nw[e[i].to][j] += nw[now][j - 1];
			dfs_g(e[i].to, now);
			for (int j = 1; j < n; j++)//换回来
				nw[e[i].to][j] -= nw[now][j - 1];
			for (int j = 1; j < n; j++)
				nw[now][j] += nw[e[i].to][j - 1];
		}
	for (int i = 1; i <= p; i++)//计算(两个都是从 now 出发,一个向 now 子树,一个往外)
		gp[now] += g[now][i] * f[now][p - i];
	for (int i = 1; i <= q; i++)
		gq[now] += g[now][i] * f[now][q - i];
}

int main() {
//	freopen("intersection.in", "r", stdin);
//	freopen("intersection.out", "w", stdout);
	
	scanf("%d %d %d", &n, &p, &q);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	dfs_f(1, 0);
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < n; j++)
			nw[i][j] = f[i][j];
	dfs_g(1, 0);
	
	ll lsum = 0, rsum = 0;//按上面进行计算
	for (int i = 1; i <= n; i++)
		lsum += fp[i], rsum += fq[i];
	ans = lsum * rsum;
	for (int i = 1; i <= n; i++)
		ans -= fp[i] * fq[i] + fp[i] * gq[i] + gp[i] * fq[i];
	
	printf("%lld", ans * 4ll);//记得要乘4(ab可以互换,cd可以互换)
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值