AtCoder - ABC 165 - F(DFS树) | 163 - F(容斥原理 + DFS树)

 165 F - LIS on Tree

题意:

一棵有 n 个结点的树,每个结点有一个值,输出每个从根节点 1 到其他点最短路的值序列中 LIS 的长度。

数据范围:

2 ≤ N ≤ 2 × 10^{5}

1 ≤ ai​ ≤ 10^{9}

1 ≤ ui​ , vi​ ≤ N

ui​ != vi​

思路:

参考博客

存好图之后dfs,相当于维护一个最长单调序列 b。

每次在 a 数组中遍历到一个新的节点,与此时的 b 数组已得到的LIS序列(单调递增的)的值判断后:如果该值大于 b 数组最后一个值,对该节点的值进行入队;否则找到第一个大于该值的位置在b 数组中进行替换操作,

在走完这个节点的子树之后,注意到树中不同链的LIS是互不影响的,所以我们回溯时需要还原,回溯就意味着我们需要改变 b 数组现存的LIS。因为这个子树中某些节点可能已经插入到数组 b中,我们要消除这个子树中节点对以后的影响,所以将 b 数组的长度减少;如果存在替换操作就把 b 数组还原。

Code:

#include<bits/stdc++.h>
using namespace std;

#define x first
#define y second
#define int long long
#define LL long long 
#define IOS ios::sync_with_stdio(false);cin.tie(0);
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 200010, INF = 0x3f3f3f3f, mod = 1e9 + 7;

typedef pair<int, int>PII;

int a[N], ans[N];           //a数组存每个节点的权值,ans数组存答案
int b[N], len;              //b数组存此时遍历到的点的路径中的LIS,len表示此时的LIS长度
vector<int>e[N];            //e二维数组存边

void dfs(int u, int pre)                             //u表示当前节点,pre表示当前节点的父节点
{
	int id = lower_bound(b, b + len, a[u]) - b;      //在b数组的LIS序列中找到值大于等于a[u]的位置
	int del = -1;                                    //del用于判断是否出现需要在已有数的b数组LIS中替换数
    
	if (id == len)len++;                             //如果id和len相等,说明a[u]的值比b数组的值都大,接在b数组后面即为新的LIS
	else del = b[id];                                //如果不相等,说明该节点u之前的节点值大于a[u],不能在现有的LIS序列中接a[u],需要替换,所以先用del记录下原来b数组中id位置的值
    
	b[id] = a[u];                                    //将b数组中id位置更新为a[u]
	ans[u] = len;                                    //此时从节点1到节点u的LIS即为len

	for (auto v : e[u])
		if (v != pre)
			dfs(v, u);                               //dfs递归计算出所有节点的LIS

	if (del == -1)--len;                             //该子树遍历完后,即该层递归结束后,需要回溯
	else b[id] = del;                                //用del还原b数组id位置的值
}

void solve()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)cin >> a[i];       //输入树中每个节点的权值
    
	for (int i = 0; i < n - 1; i++)
	{
		int u, v;
		cin >> u >> v;
		u--, v--;
		e[u].push_back(v);                       //二维数组存边
		e[v].push_back(u);
	}

	dfs(0, -1);                                  //DFS找出每个从根节点 1 到其他点最短路的值序列中LIS的长度

	for (int i = 0; i < n; i++)
		cout << ans[i] << endl;
}

signed main()
{
	IOS;

	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

	return 0;
}

163 F- path pass i 

题意:

给一棵 N 个节点的无根树,每个节点有一个值 c,该值表示每个节点的颜色,数值相同说明颜色相同,对于每个颜色,求经过这种颜色的简单路径的数量。

数据范围:

1 ≤ N ≤ 2 × 10^{5}

1 ≤ ci​ ≤ N

1 ≤ ai​, bi ​≤ N

思路:

参考博客

如果算包含一种颜色的点的路径,是很困难的。(1-2-3包含2,2-3也包含2,2也包含2,1-2也包含……要是点的数目大,似乎无规律可循)
算一个连通块的路径的数目,就是 n*(n-1)/2(排列组合Cn2),注意在这道题中,只有一个点也算一条路径,那么加上 n,得到算一个连通块的路径的数目为 n*(n+1)/2 。那么问题就可以转化为,求“路径总数 - 不包含颜色为i的点的所有连通块内部的路径数”。
接下来就只用找出不包含每种颜色的所有连通块的大小————
树形DFS,一遍把树全部遍历O(n)即可;

实现:

1. 用二维数组存树
2. 纵向DFS:一直DFS到最底层之后,回溯从叶子往根走的过程中,每遇到一种颜色,马上计算上一次(也是在回溯过程中)遇到的这种颜色离这里的位置,就可以得出之间并非此颜色的节点数目。更新一次答案。把这种颜色的最近位置更新;
那么回溯到根节点的时候,除了根节点颜色以外的其他颜色都没有进行最后一次更新(根附近的并非此颜色的连通块没有被计算),这就在DFS结束之后补上。
3. 横向DFS:因为是树嘛,可能不是一条直链,而有很多分链,DFS的时候就要考虑到这些分链的作用。于是每到DFS一个节点的时候,都要记录当前的数目,每次进入DFS之前,记录一下,回来之后,计算一下改变量。

这是整体思路,具体代码实现还是有亿点点细节处理。理解不深,写不出来细节怎么实现的……

Code:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;

#define x first
#define y second
#define int long long
const int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };

const int N = 200010, INF = 0x3f3f3f3f, mod = 1e9 + 7;

typedef pair<int, int>PII;

int a[N], siz[N], sum[N];      //siz[i]表示i的子树(包括i)的节点个数,sum[i]代表以颜色为i的节点(其祖先没有颜色为1的节点)为根节点的子树节点中颜色为i的节点个数和
vector<int>G[N];
int ans[N];

int cal(int x)
{
	return x * (x + 1) / 2;    //算一个连通块的路径的数目,即n*(n-1)/2;该题中,一个点也算一条路径,加上n,得到算一个连通块的路径的数目为n*(n+1)/2
}

void dfs(int u, int fa)
{
	//int c = a[u], save = sum[c];  
	siz[u] = 1;
	int pre = sum[a[fa]], t = sum[a[u]];                      //这个pre和t都是遍历到u之前的

	for (auto &v : G[u])
	{
		if (v == fa)continue;
		dfs(v, u);
		siz[u] += siz[v];                                     //更新节点u子树大小
	}
	sum[a[u]] = t + siz[u];
	ans[a[fa]] -= cal(siz[u] - (sum[a[fa]] - pre));
}

void solve()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];    //输入n个点的颜色

	for (int i = 1; i <= n - 1; i++)
	{
		int u, v;
		cin >> u >> v;

		G[u].push_back(v);                     //存边
		G[v].push_back(u);
	}

	for (int i = 1; i <= n; i++)
		ans[i] = cal(n);                       //初始值为n个点的连通块的总路径数

	dfs(1, 0);

	for (int i = 1; i <= n; i++)
	{
		ans[i] -= cal(n - sum[i]);             //把离根最近的连通块补上
		cout << ans[i] << endl;
	}
}

signed main()
{
	//int t;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

	return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:两题都用到了DFS树,所以就写一起了,不过后一个题难度上了一个层次,在DFS的基础上让我感觉更难理解了orz,虽然看半天就看懂了七七八八,但是还是放上吧,说不定以后能完全看懂的说不定。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值